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.