Combobox
A text field that filters a list as you type. The field is Koala's Input and the list is Select's panel, so it sits beside both without a seam. Autocomplete is the free-text sibling.
Installation#
Usage#
import {
Combobox, ComboboxInput, ComboboxContent, ComboboxItem, ComboboxEmpty,
} from "@/components/ui/combobox"
export function Example() {
const [value, setValue] = useState("")
return (
<Combobox value={value} onValueChange={setValue}>
<ComboboxInput aria-label="Framework" placeholder="Pick a framework" />
<ComboboxContent>
<ComboboxItem value="next">Next.js</ComboboxItem>
<ComboboxItem value="remix">Remix</ComboboxItem>
<ComboboxEmpty />
</ComboboxContent>
</Combobox>
)
}Anatomy#
import {
Combobox, Autocomplete, ComboboxInput, ComboboxContent, ComboboxItem,
ComboboxGroup, ComboboxLabel, ComboboxSeparator, ComboboxEmpty, ComboboxStatus,
} from "@/components/ui/combobox"
<Combobox> {/* or <Autocomplete> */}
<ComboboxInput />
<ComboboxContent>
<ComboboxGroup>
<ComboboxLabel />
<ComboboxItem />
</ComboboxGroup>
<ComboboxSeparator />
<ComboboxStatus />
<ComboboxEmpty />
</ComboboxContent>
</Combobox>Icons and flags#
Rows take a leading icon like Select rows do, muted beside a medium label. Pass the picked option's glyph to prefix so the field shows it too. For countries, use Flag and match on the ISO code with keywords.
Groups#
ComboboxGroup with a ComboboxLabel heading, split by ComboboxSeparator. A group hides once all its rows filter out, and separators step aside while you type, so no orphan rules are left.
Clearable#
The clear button shows while the field holds text. It stays out of the tab order because Escape does the same from the keyboard. Turn it off with showClear={false}.
Async search#
Control inputValue to fetch as the user types, and pass filter={null} so the server's results show as they are. ComboboxStatus holds the list's place while a request is in flight and is announced politely.
Creatable#
Add a row for the typed text when nothing matches it exactly. Its textValue is the query, so the filter always keeps it, and picking it commits the new value like any other row.
Autocomplete#
Autocomplete takes the same parts, but the text is the value: anything typed is valid, and a suggestion only fills it in. Nothing is highlighted until you arrow down, so Enter keeps what you wrote. Rows carry no check.
Inside a Field#
In a Field, the label names both the field and the list, the hint is wired as its description, and hasError and disabled cascade. Pass name to post the value with the form.
Disabled#
disabled on the root locks the whole field. On a row, it stays visible but the keyboard skips it and a click does nothing.
Sizes#
size sets the field height on the shared control scale: sm 32, md 36, lg 40px, level with Input, Select and Button. Typed text stays 14px at every size.
Keyboard#
Focus never leaves the field. Arrow Down and Arrow Up open the list and move the highlight; Home and End jump to the ends while it is open. Enter picks the highlighted row. Escape first clears what you typed, then closes the list, then clears the value. Tab closes and moves on; leaving with exactly an option's text picks it, anything else reverts.
API reference#
Combobox
Root. Owns the value, the typed text and the open state. Renders no DOM of its own.
| Prop | Type | Default | Description |
|---|---|---|---|
| value | string | - | Controlled selected value. "" means nothing is selected. |
| defaultValue | string | "" | Initial value when uncontrolled. |
| onValueChange | (value: string) => void | - | Called when an option is picked or the value is cleared. |
| inputValue | string | - | Controlled text in the field, e.g. to drive a search request. |
| defaultInputValue | string | - | Initial text when uncontrolled. |
| onInputValueChange | (inputValue: string) => void | - | Called on every keystroke, pick, clear and revert. |
| itemToString | (value: string) => string | - | Label shown for a value seeded from outside, before the list has rendered. |
| autoHighlight | boolean | true | Highlight the first match while typing, so Enter picks it. |
| openOnInputClick | boolean | true | Open the list when the field is clicked. |
| size | "sm" | "md" | "lg" | - | Field height, shared with Input, Select and Button (32, 36, 40px). Defaults from density. |
| density | "comfortable" | "compact" | - | Row spacing in the list. Also picks the default size. |
| open | boolean | - | Controlled open state of the list. |
| defaultOpen | boolean | false | Open state on mount when uncontrolled. |
| onOpenChange | (open: boolean) => void | - | Called when the list opens or closes. |
| filter | (query: string, textValue: string, keywords?: string[]) => number | null | contains | Return 0 to hide a row. Case and accent blind by default. Pass null to show every row (server results). |
| name | string | - | Posts the value with a native form submit. |
| disabled | boolean | - | Disables the field. Inherited from a surrounding Field. |
Autocomplete
Root for free text. The field's text is the value; picking a row fills it in. Takes the same parts.
| Prop | Type | Default | Description |
|---|---|---|---|
| value | string | - | Controlled text. |
| defaultValue | string | "" | Initial text when uncontrolled. |
| onValueChange | (value: string) => void | - | Called on every keystroke and pick. |
| autoHighlight | boolean | false | Off by default, so Enter keeps what was typed. |
| openOnInputClick | boolean | false | Opens on typing only, by default. |
| size | "sm" | "md" | "lg" | - | Field height, shared with Input, Select and Button (32, 36, 40px). Defaults from density. |
| density | "comfortable" | "compact" | - | Row spacing in the list. Also picks the default size. |
| open | boolean | - | Controlled open state of the list. |
| defaultOpen | boolean | false | Open state on mount when uncontrolled. |
| onOpenChange | (open: boolean) => void | - | Called when the list opens or closes. |
| filter | (query: string, textValue: string, keywords?: string[]) => number | null | contains | Return 0 to hide a row. Case and accent blind by default. Pass null to show every row (server results). |
| name | string | - | Posts the value with a native form submit. |
| disabled | boolean | - | Disables the field. Inherited from a surrounding Field. |
ComboboxInput
The canonical Input with role="combobox", a clear button and a chevron. Other props go to the <input>.
| Prop | Type | Default | Description |
|---|---|---|---|
| prefix | ReactNode | - | A leading glyph: a search icon, the picked option's flag. |
| showClear | boolean | true | Show the clear button while the field holds text. |
| showTrigger | boolean | true (Combobox), false (Autocomplete) | Show the chevron that opens the list. |
| className | string | - | Styles the field's frame. |
| inputClassName | string | - | Styles the text input. |
| Data attribute | Description |
|---|---|
| data-state | open or closed, on the frame. |
ComboboxContent
The floating listbox, anchored to the field and exactly its width. Accepts Radix Popover content props.
| Prop | Type | Default | Description |
|---|---|---|---|
| label | string | "Options" | Accessible name, when there is no Field label to borrow. |
| align | "start" | "center" | "end" | "start" | Alignment against the field. |
| sideOffset | number | 6 | Gap from the field, in px. |
| Data attribute | Description |
|---|---|
| data-side | top or bottom, after collision handling. |
| CSS variable | Description |
|---|---|
| --radix-popover-content-available-height | Caps the list height so it scrolls inside the viewport. |
ComboboxItem
An option row: Select's anatomy, with the brand check on the right.
| Prop | Type | Default | Description |
|---|---|---|---|
| value* | string | - | The value this row commits. |
| textValue | string | row text | What the filter matches and what the field shows once picked. |
| keywords | string[] | - | Extra terms that also match (a code, a synonym). |
| disabled | boolean | - | Skipped by the keyboard and not pickable. |
| onSelect | (value: string) => void | - | Fires when the row is picked. |
| Data attribute | Description |
|---|---|
| data-active | Present on the highlighted row. |
| data-state | checked on the selected row. |
| data-disabled | Present when disabled. |
ComboboxGroup
A group of rows. Hides itself when every row in it filters out.
ComboboxLabel
A group heading, muted.
ComboboxSeparator
A hairline between groups. Hidden while filtering.
ComboboxEmpty
Shown only while the list has no rows. Defaults to "No results found."
ComboboxStatus
A polite live message in the list: searching, an error, a hint.
| Prop | Type | Default | Description |
|---|---|---|---|
| loading | boolean | - | Lead with the Spinner. |