Skip to main content

Combobox

A combo-box where the trigger is the input itself. Type to filter options, use ↑ / ↓ to navigate the dropdown, Enter to select, Escape to dismiss, and × to clear. Supports outlined and filled variants, sm / md / lg sizes, option icons, sub-labels, helper / error states, async loading skeletons, and multi-select with chip tags.

Basic

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

const FRUITS = [
{ id: "apple", label: "Apple", subLabel: "Red or green" },
{ id: "banana", label: "Banana", subLabel: "Tropical" },
{ id: "cherry", label: "Cherry", subLabel: "Stone fruit" },
{ id: "mango", label: "Mango", subLabel: "Tropical" },
{ id: "orange", label: "Orange", subLabel: "Citrus" },
{ id: "pear", label: "Pear", subLabel: "Temperate" },
{ id: "pineapple", label: "Pineapple", subLabel: "Tropical" },
{ id: "strawberry", label: "Strawberry", subLabel: "Berry" },
];

const App = () => {
const selected = atom(null);
return () => html`
<x-col gap="var(--md-space-3)" style="max-width:22rem;width:100%;">
${AutoComplete({ options: FRUITS, value: selected(), label: "Favourite fruit", placeholder: "Search fruits…", onChange: (id) => selected.set(id) })}
${Text("Selected: " + (selected() ?? "none"), { type: "body-sm", color: "muted" })}
</x-col>
`;
};

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

Variants & Sizes

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

const OPTIONS = [
{ id: "us", label: "United States", subLabel: "North America" },
{ id: "uk", label: "United Kingdom", subLabel: "Europe" },
{ id: "de", label: "Germany", subLabel: "Europe" },
{ id: "fr", label: "France", subLabel: "Europe" },
{ id: "jp", label: "Japan", subLabel: "Asia" },
];

const App = () => {
const v1 = atom(null); const v2 = atom(null); const v3 = atom(null);
const v4 = atom(null); const v5 = atom(null); const v6 = atom(null);
return () => html`
<x-row gap="var(--md-space-5)" style="max-width:48rem;" align="flex-start">
<x-col gap="var(--md-space-3)" style="flex:1;">
${AutoComplete({ options: OPTIONS, value: v1(), label: "Outlined sm", size: "sm", onChange: (id) => v1.set(id) })}
${AutoComplete({ options: OPTIONS, value: v2(), label: "Outlined md", size: "md", onChange: (id) => v2.set(id) })}
${AutoComplete({ options: OPTIONS, value: v3(), label: "Outlined lg", size: "lg", onChange: (id) => v3.set(id) })}
</x-col>
<x-col gap="var(--md-space-3)" style="flex:1;">
${AutoComplete({ options: OPTIONS, value: v4(), label: "Filled sm", variant: "filled", size: "sm", onChange: (id) => v4.set(id) })}
${AutoComplete({ options: OPTIONS, value: v5(), label: "Filled md", variant: "filled", size: "md", onChange: (id) => v5.set(id) })}
${AutoComplete({ options: OPTIONS, value: v6(), label: "Filled lg", variant: "filled", size: "lg", onChange: (id) => v6.set(id) })}
</x-col>
</x-row>
`;
};

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

Icons & Sub-labels

import { html, atom, renderApp } from 'mates';
import { AutoComplete, Text } from 'mates-ui';
import { CodeIcon, AdminPanelSettingsIcon, EditIcon, VisibilityIcon, StarIcon } from 'mates-icons';

const LANGUAGES = [
{ id: "ts", label: "TypeScript", subLabel: "Typed JS superset", icon: CodeIcon() },
{ id: "rs", label: "Rust", subLabel: "Systems language", icon: CodeIcon() },
{ id: "py", label: "Python", subLabel: "Scripting", icon: CodeIcon() },
{ id: "go", label: "Go", subLabel: "Google language", icon: CodeIcon() },
];

const ROLES = [
{ id: "admin", label: "Admin", subLabel: "Full access", icon: AdminPanelSettingsIcon() },
{ id: "editor", label: "Editor", subLabel: "Can edit", icon: EditIcon() },
{ id: "viewer", label: "Viewer", subLabel: "Read only", icon: VisibilityIcon() },
{ id: "owner", label: "Owner", subLabel: "Ownership", icon: StarIcon() },
];

const App = () => {
const lang = atom(null);
const role = atom(null);
return () => html`
<x-row gap="var(--md-space-5)" style="max-width:48rem;" align="flex-start">
<x-col gap="var(--md-space-2)" style="flex:1;">
${AutoComplete({ options: LANGUAGES, value: lang(), label: "Programming language", placeholder: "Search…", onChange: (id) => lang.set(id) })}
${lang() ? Text("Selected: " + lang(), { type: "body-sm", color: "muted" }) : html``}
</x-col>
<x-col gap="var(--md-space-2)" style="flex:1;">
${AutoComplete({ options: ROLES, value: role(), label: "Assign role", placeholder: "Pick a role…", onChange: (id) => role.set(id) })}
${role() ? Text("Selected: " + role(), { type: "body-sm", color: "muted" }) : html``}
</x-col>
</x-row>
`;
};

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

States

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

const OPTIONS = [
{ id: "us", label: "United States", subLabel: "North America" },
{ id: "uk", label: "United Kingdom", subLabel: "Europe" },
{ id: "de", label: "Germany", subLabel: "Europe" },
];

const App = () => {
const v1 = atom(null); const v2 = atom(null);
return () => html`
<div style="display:grid;grid-template-columns:1fr 1fr;gap:1.5rem;max-width:48rem;width:100%;">
${AutoComplete({ options: OPTIONS, value: v1(), label: "With helper", helper: "Start typing a country name", onChange: (id) => v1.set(id) })}
${AutoComplete({ options: OPTIONS, value: v2(), label: "With error", error: "Please select a valid country", onChange: (id) => v2.set(id) })}
${AutoComplete({ options: OPTIONS, value: "de", label: "Disabled", disabled: true, onChange: () => {} })}
${AutoComplete({ options: [], value: null, label: "Loading", loading: true, skeletonCount: 4, onChange: () => {} })}
</div>
`;
};

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

Multi-select

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

const OPTIONS = [
{ id: "us", label: "United States", subLabel: "North America" },
{ id: "uk", label: "United Kingdom", subLabel: "Europe" },
{ id: "de", label: "Germany", subLabel: "Europe" },
{ id: "fr", label: "France", subLabel: "Europe" },
{ id: "jp", label: "Japan", subLabel: "Asia" },
];

const App = () => {
const selected = atom([]);
return () => html`
<x-col gap="var(--md-space-3)" style="max-width:26rem;width:100%;">
${AutoComplete({ options: OPTIONS, multiple: true, values: selected(), label: "Countries", placeholder: "Select countries…", maxVisibleTags: 3, onChangeMultiple: (ids) => selected.set(ids) })}
${Text(selected().length === 0 ? "No selection" : "Selected (" + selected().length + "): " + selected().join(", "), { type: "body-sm", color: "muted" })}
</x-col>
`;
};

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

AutoComplete

import { AutoComplete } from "mates-ui"; import type { AutoCompleteOption } from "mates-ui";
Prop Type Default Description
attr AttrMap — HTML attributes on the root element.
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 entire control.
emptyText string "No options" Text shown when no options match the typed query.
error string — Error message shown below the field; also applies error styling.
helper string — Helper text shown below the field in muted colour.
id string — Root `id` shorthand (`attr.id` wins). Form native-control `id` props are unchanged.
label string — Floating label above the input.
loading boolean false Replaces list items with skeleton rows while data loads.
maxHeight string "17.5rem" Max height of the dropdown scroll area.
maxVisibleTags number 3 Max chips shown before a +N overflow badge appears (multiple mode).
multiple boolean false Enable multi-select mode. Use values / onChangeMultiple.
on OnEventMap — DOM event handlers on the root element.
onChange (value: string | null, option: AutoCompleteOption | null) => void — Called on selection or clear (single mode).
onChangeMultiple (values: string[], options: AutoCompleteOption[]) => void — Called when the multi-selection changes.
options* AutoCompleteOption[] — Full list of options. The component handles filtering internally.
placeholder string "Search…" Placeholder shown when the field is empty.
popupMinWidth string — CSS min-width of the floating panel. Defaults to the input width.
size "sm" | "md" | "lg" "md" Controls field height and font size.
skeletonCount number — Number of skeleton rows shown when loading is true.
style StyleMap — Inline CSS on the root element.
value string | null — Controlled selected option id. Pass null for no selection.
values string[] — Controlled array of selected ids (multiple mode).
variant "outlined" | "filled" "outlined" Visual style of the text field border.