Skip to main content

TextSelect

An inline text-based select — plain selected-value label + chevron, no border, no background. Reflects the chosen option directly in the trigger. Perfect for toolbars, table headers, and inline sentences.

Quick start
import { TextSelect } from "mates-ui";
const size = atom("md");

TextSelect({
  options: [
    { id: "sm", label: "Small" },
    { id: "md", label: "Medium" },
    { id: "lg", label: "Large" },
  ],
  value: size(),
  onChange: (id) => size.set(id),
  placeholder: "Pick size…",
});

Basic

import { html, atom, renderApp } from 'mates';
import { TextSelect, Text } from 'mates-ui';

const App = () => {
const font = atom(""); const theme = atom(""); const lang = atom("");
return () => html`
<x-col gap="var(--md-space-4)">
<x-row gap="var(--md-space-5)" align="center" wrap>
${TextSelect({ options: [{ id: "sm", label: "Small" }, { id: "md", label: "Medium" }, { id: "lg", label: "Large" }, { id: "xl", label: "Extra Large" }], value: font(), onChange: (id) => font.set(id), placeholder: "Font size…", popup: { position: "bottom-start" } })}
${TextSelect({ options: [{ id: "light", label: "Light" }, { id: "dark", label: "Dark" }, { id: "system", label: "System" }], value: theme(), onChange: (id) => theme.set(id), placeholder: "Theme…", popup: { position: "bottom-start" } })}
${TextSelect({ options: [{ id: "en", label: "English" }, { id: "fr", label: "Français" }, { id: "de", label: "Deutsch" }, { id: "ja", label: "日本語" }], value: lang(), onChange: (id) => lang.set(id), placeholder: "Language…", popup: { position: "bottom-start" } })}
</x-row>
${Text("Font: " + (font() || "—") + " · Theme: " + (theme() || "—") + " · Lang: " + (lang() || "—"), { type: "body-sm", color: "muted" })}
</x-col>
`;
};

renderApp(App, document.getElementById('app'));

Sizes

import { html, atom, renderApp } from 'mates';
import { TextSelect, Text } from 'mates-ui';

const OPTS = [{ id: "sm", label: "Small" }, { id: "md", label: "Medium" }, { id: "lg", label: "Large" }];

const App = () => {
const sm = atom(""); const md = atom(""); const lg = atom("");
return () => html`
<x-col gap="var(--md-space-3)">
<x-row gap="var(--md-space-4)" align="center">
${Text("sm", { type: "label-sm", color: "muted" })}
${TextSelect({ size: "sm", options: OPTS, value: sm(), onChange: (id) => sm.set(id), placeholder: "Pick size…", popup: { position: "bottom-start" } })}
</x-row>
<x-row gap="var(--md-space-4)" align="center">
${Text("md", { type: "label-sm", color: "muted" })}
${TextSelect({ size: "md", options: OPTS, value: md(), onChange: (id) => md.set(id), placeholder: "Pick size…", popup: { position: "bottom-start" } })}
</x-row>
<x-row gap="var(--md-space-4)" align="center">
${Text("lg", { type: "label-sm", color: "muted" })}
${TextSelect({ size: "lg", options: OPTS, value: lg(), onChange: (id) => lg.set(id), placeholder: "Pick size…", popup: { position: "bottom-start" } })}
</x-row>
</x-col>
`;
};

renderApp(App, document.getElementById('app'));

Inline in text

import { html, atom, renderApp } from 'mates';
import { TextSelect } from 'mates-ui';

const App = () => {
const perPage = atom("10");
const sortBy = atom("name");
const order = atom("asc");
return () => html`
<p style="font-size:var(--md-text-base);color:var(--md-color-text);line-height:2;">
Show
${TextSelect({ options: [{ id: "10", label: "10" }, { id: "25", label: "25" }, { id: "50", label: "50" }, { id: "100", label: "100" }], value: perPage(), onChange: (id) => perPage.set(id), popup: { position: "bottom-start" } })}
items per page, sorted by
${TextSelect({ options: [{ id: "name", label: "name" }, { id: "date", label: "date" }, { id: "size", label: "size" }], value: sortBy(), onChange: (id) => sortBy.set(id), popup: { position: "bottom-start" } })},
in
${TextSelect({ options: [{ id: "asc", label: "ascending" }, { id: "desc", label: "descending" }], value: order(), onChange: (id) => order.set(id), popup: { position: "bottom-start" } })}
order.
</p>
`;
};

renderApp(App, document.getElementById('app'));

Disabled

import { html, renderApp } from 'mates';
import { TextSelect } from 'mates-ui';

const OPTS = [{ id: "sm", label: "Small" }, { id: "md", label: "Medium" }, { id: "lg", label: "Large" }];

const App = () => () => html`
<x-row gap="var(--md-space-5)" align="center">
${TextSelect({ options: OPTS, value: "md", disabled: true, popup: { position: "bottom-start" } })}
${TextSelect({ options: OPTS, placeholder: "Pick size…", disabled: true, popup: { position: "bottom-start" } })}
</x-row>
`;

renderApp(App, document.getElementById('app'));

vs TextMenu

import { html, atom, renderApp } from 'mates';
import { TextMenu, TextSelect, Text } from 'mates-ui';

const FORMAT_OPTS = [{ id: "bold", label: "Bold" }, { id: "italic", label: "Italic" }, { id: "underline", label: "Underline" }, { id: "strikethrough", label: "Strikethrough" }];

const App = () => {
const formatVal = atom("");
return () => html`
<x-row gap="var(--md-space-8)" align="flex-start" wrap>
<x-col gap="var(--md-space-2)">
${Text("TextMenu — label never changes", { type: "label-sm", color: "muted" })}
${TextMenu({ label: "Format", list: { items: FORMAT_OPTS.map((o) => ({ id: o.id, label: o.label })), onSelect: () => {} }, popup: { position: "bottom-start" } })}
</x-col>
<x-col gap="var(--md-space-2)">
${Text("TextSelect — reflects chosen value", { type: "label-sm", color: "muted" })}
${TextSelect({ options: FORMAT_OPTS, value: formatVal(), onChange: (id) => formatVal.set(id), placeholder: "Format…", popup: { position: "bottom-start" } })}
</x-col>
</x-row>
`;
};

renderApp(App, document.getElementById('app'));

TextSelect

import { TextSelect } from "mates-ui";
Prop Type Default Description
attr AttrMap — Attribute overrides on internal elements.
classes string | string[] — Extra root classes — joined and appended after built-in classes.
data DataAttrMap — Root `data-*` map (`testId` → `data-testid`). `testId` prop / `attr` win over `data`.
disabled boolean false Disables the trigger.
emptyText string — Text shown in the panel when there are no items.
error boolean — Puts the field into an error state.
helper string — Helper text shown below the trigger.
id string — Root `id` shorthand (`attr.id` wins). Form native-control `id` props are unchanged.
items* SelectItem[] — Array of selectable options.
label string — Label text displayed above the trigger.
maxHeight string — CSS max-height for the dropdown panel (e.g. '16rem').
on OnEventMap — DOM event handlers on the root element.
onChange (id: string) => void — Called when user picks an option. Receives the selected id.
placeholder string "Select…" Text shown when no value is selected.
showRadio boolean — Shows a radio indicator next to the selected item.
size "sm" | "md" | "lg" "md" Visual size — maps to text-xs / text-sm / text-base.
style StyleMap — Shorthand inline styles applied to the root button element.
style StyleMap — Inline CSS on the root element.
testId string — Data-testid attribute forwarded to the root element for testing.
value string | null — Currently selected option id. Use null for no selection.

CSS tokens

Token Role
--md-color-primary Trigger color on hover and when open.
--md-color-text Trigger color when a value is selected.
--md-color-text-muted Chevron icon color.
--md-color-text-placeholder Trigger color when no value selected.