Combobox
v1.0.23Updated 20 Oct 2026Searchable single- or multi-select that composes Popover + Command. Hand-rolled — single via Combobox, multi via MultiCombobox.
Preview
Live · 11 stories▸Why this is recommendedcomboboxselectsearchautocomplete+4
The default, out-of-the-box Combobox.
- ✓Choice fields with more than 10 options
- ✓Country / language pickers with search
- ✓Owner or assignee selectors on a task form
- ✓Picking a framework, category, or status from a long list
- ✓Any select where the user needs to type to narrow the list
- Searchable single-select with a filter input
- Opt-in multiple mode (checkbox rows + chips)
- Flat or grouped options with headings and separators
- Custom trigger via the `trigger` prop
- Server-side filtering hook via `onSearchChange`
- Disabled options and disabled field
- Composes Popover + Command
- Labelled by FormField: id and aria-* land on the role=combobox trigger, which then fills the field column
- ✕Small choice sets (use Select or RadioGroup)
- ✕Free-text entry (use Input)
- ✕Options fetched per keystroke with loading and error states (use ComboboxAsync)
- ✕Dedicated multi-select with an overflow count (use MultiCombobox)
Install
Ask your agent, or call the MCP tool directly. The resolved bundle is written into your project.
Usage
Tick variants in the left menu — each adds a tab with that variant’s real story source, the same code the preview above renders.
Props
optionsFlat or grouped options; grouped entries render headings with separators
yesvalueSelected value — string in single mode, string[] when `multiple`
—onValueChangeFires on select/deselect; signature follows `multiple`
—multipleMulti-select mode: checkbox rows, chips in the trigger, popover stays open
—placeholderTrigger text when nothing is selected (default "Select…")
—searchPlaceholderPlaceholder for the filter input (default "Search…")
—emptyMessageShown when no option matches (default "No results.")
—onSearchChangeCalled as the user types — use it to fetch/filter options server-side. Also called with an empty string when the popover closes, because the search field is discarded with it, so a host that mirrors the query never keeps a stale one
—onOpenChangeFires on every open and close of the popover, including the programmatic close after a selection
—triggerReplace the default trigger button; the forwarded ref stays on the default trigger
—disabledDisable the trigger
—contentClassNameClass applied to the popover content
—scrollbarHow the option list draws its scrollbar once it overflows — forwarded to CommandList. Default 'thin': a visible themed bar, because the height cap is the only thing hiding the remaining options
—listMaxHeightCap the option list's height (number = px, string = any CSS length such as '50vh'). Omitted, the list keeps CommandList's 18rem default
—idDOM id set on the trigger — a <label htmlFor> (or FormField) names the control through it
—aria-labelAccessible name for the trigger when there is no visible label
—aria-labelledbyId(s) of the visible label element(s) that name the trigger
—aria-describedbyId(s) of hint or error text that describe the trigger
—aria-invalidMark the trigger invalid — FormField clones this in from its error prop
—aria-requiredMark the trigger required — FormField clones this in from its required prop
—invalidMark the field invalid (destructive ring); aria-invalid wins when both are set
—requiredMark the field required (aria-required on the trigger)
—classNameClass applied to the trigger; a width here wins over the default (w-full inside a FormField, the standalone width otherwise)
—