Type-to-filter combobox: matched text lights up in volt, a create row offers what you typed, async results load behind a spinner, and an empty state wobbles in when nothing matches.
comboboxautocompletesearchselectformasync
Installation
$npx shadcn@latest add @geturui/combobox
Usage
usage.tsx
1import * as React from "react";
2import { Combobox, type ComboboxOption } from "@/components/ui/combobox";
3
4export function LabelPicker() {
5 const [labels, setLabels] = React.useState<ComboboxOption[]>([
6 { value: "bug", label: "Bug", dot: "danger" },
7 { value: "design", label: "Design", dot: "info" },
8 ]);
9 const [value, setValue] = React.useState<string | null>(null);
10 return (
11 <Combobox
12 label="Label"
13 options={labels}
14 value={value}
15 onValueChange={setValue}
16 onCreate={(text) => {
17 setLabels((l) => [...l, { value: text, label: text }]);
18 setValue(text);
19 }}
20 />
21 );
22}
Props
| Prop | Type | Default | Description |
|---|---|---|---|
options* | ComboboxEntry[] | — | { value, label, description?, icon?, avatar?: boolean | string, dot?: "success" | "warning" | "danger" | "info" | "neutral", meta?, shortcut?, disabled?, keywords? }, a group { heading, options } or { separator: true } |
value / defaultValue / onValueChange | string | null | — | Selected value, controlled or uncontrolled. |
inputValue / defaultInputValue | string | — | Text in the input, controlled or uncontrolled. |
onInputChange | (text) => void | — | Every keystroke. Fetch here and pass the results back as options. |
filter | false | (option, query) => boolean | — | Custom match test, or false when options arrive already filtered. |
loading | boolean | false | Spinner in the well, plus a loading row while there are no results. |
onCreate | (text) => void | — | Adds a Create “text” row when nothing matches exactly. |
createLabel | (text) => string | — | Wording for the create row. |
emptyText / emptyDescription | ReactNode | — | Empty state title and hint. |
size | "sm" | "md" | "lg" | "md" | Well height 32 / 40 / 48, matching Button. Rows, icons and the list height scale with it. |
label / hint / error | ReactNode | — | Field chrome. Any error marks the control invalid and swaps the hint for the message. |
required | boolean | false | Volt asterisk on the label and aria-required on the control. |
disabled | boolean | false | Dims the well and blocks interaction. |
name | string | — | Form field name; values post through hidden inputs. |
placeholder | string | "Search" | Input placeholder. |
iconLeading | ComponentType<{ className }> | Search | Well icon, replaced by the selected option's media. |
openOnFocus | boolean | false | Open the list as soon as the input is focused. |
Accessibility
- APG combobox with list autocomplete: focus stays in the input and aria-activedescendant tracks the highlighted option.
- Down/Up open and move, Enter picks, Escape closes and a second Escape clears the text.
- Leaving the field restores the selected label, or clears the value when the text was emptied.
- Loading and empty states are announced through role=status; the clear and toggle buttons have labels and stay out of the tab order.