Skip to content
GitHub

Forms

Combobox

A select you can type into.

When to reach for it

Past roughly a dozen options. Below that a native Select is better: the platform picker on a phone, typeahead for free, no JavaScript.

Examples

default

Type to filter — “compact” matches two of them.

multiple

The panel stays open while you pick; past two it counts instead of listing.

Notes

A select you can type into, choosing one or several.

The line against Select is length, and it is not a matter of taste: a styled select is better up to roughly a dozen options, because a list nobody can filter is faster to scan than one they have to think about. Past that, this is the right answer.

Filtering, the highlighted row and the arrow keys come from cmdk, which implements the ARIA combobox pattern properly: the highlight moves through aria-activedescendant while focus stays in the input. Hand-rolled comboboxes move focus into the list instead, and the typed text stops being editable.

Controlled or uncontrolled, like every other form control here.

Props

Combobox props
PropTypeDefaultDescription
labelrequiredstringNames the control. Required — the trigger's text is a value, not a label.
optionsrequiredComboboxOption[]
classNamestring
defaultValuestring
disabledbooleanfalse
emptyMessagestring'Nothing matches.'Shown when the filter matches nothing. Say what WOULD match.
multiplefalse
onValueChange(value: string) => void
placeholderstring'Select…'Shown on the trigger when nothing is chosen.
searchPlaceholderstring'Search…'Placeholder inside the filter field.
valuestring

Keyboard

Combobox keyboard interactions
KeyDoes
EnterSpaceOpens the list.
Moves the highlight while focus stays in the filter.
EnterChooses the highlighted option; choosing the current one clears it.
EscapeCloses without choosing.

Accessibility

  • The highlight moves through aria-activedescendant while focus stays in the input — the ARIA combobox pattern. Hand-rolled comboboxes move focus into the list, and the typed text stops being editable.
  • label is required: the trigger prints a value, and a value is not a name.