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.