Combobox
<Combobox> is a searchable single-select. Composed from shadcn’s canonical Command (cmdk) + Popover. Reach for it whenever the option list is long enough that scrolling the Select would be slower than typing.
When to use
- Pickers from a list of ~10–500 options (countries, owners, tags).
- Async-loaded options (search-as-you-type).
- Anywhere
Selectgets too long.
Don’t use Combobox for: short static lists ≤ ~10 (Select is faster), 2–5 visible-at-once choices (SegmentedControl / RadioGroup), or app-wide commands (CommandPalette — planned).
Default
Type to filter. Click an option to select. Click the same option again to clear.
<Combobox options={[ { value: 'us', label: 'United States' }, { value: 'ca', label: 'Canada' }, { value: 'vn', label: 'Vietnam' }, ]} value={value} onValueChange={setValue} triggerLabel="Select a country" placeholder="Search countries…"/>API
| Prop | Type | Default | Description |
|---|---|---|---|
options * | ComboboxOption<TValue>[] | — | List of options. Each: { value, label, disabled?, searchValue? }. |
value | TValue | — | Controlled. Pair with onValueChange. |
defaultValue | TValue | — | Initial value for uncontrolled mode. |
onValueChange | (value: TValue | undefined) => void | — | undefined when the user clears the selection. |
placeholder | string | "Search…" | Search-input placeholder inside the popover. |
triggerLabel | ReactNode | "Select…" | Trigger button label when no value is selected. |
emptyState | ReactNode | "No results." | Shown when the search filter matches nothing. |
ariaLabel | string | — | Required when triggerLabel is non-string and there is no surrounding Label. |
disabled | boolean | false | Disable the trigger. |
matchTriggerWidth | boolean | true | Popover content matches trigger width. |
className | string | — | Forwarded to the trigger Button. |
contentClassName | string | — | Forwarded to PopoverContent. |
ComboboxOption<TValue>
| Prop | Type | Default | Description |
|---|---|---|---|
value * | TValue | — | Unique identifier within the options list. |
label * | ReactNode | — | Visible label. |
disabled | boolean | false | Skip during keyboard nav and click. |
searchValue | string | — | Override the string used for filtering. Defaults to the label when string. |
Design guidelines
✓ Do
- Use Combobox once the option list grows past ~10. The search box earns its space.
- Use searchValue when the visible label is rich (icon + text) — typing should match the text.
- Pre-sort options by the user's frequency of use, then alphabetical for the long tail.
✗ Don't
- Use Combobox for tiny static lists. Select is faster.
- Render multi-select as a Combobox with checkboxes inside. The MultiCombobox primitive is on the roadmap.
- Skip the empty state. "No results." prevents the popover from looking broken when filters are tight.
Accessibility
- Trigger is
role="combobox"witharia-expanded. - Inner
Commandprovides full ARIA listbox semantics + keyboard. - Keyboard: Enter opens, ↑/↓ navigate, Enter on item selects, Esc closes.
- Type-to-filter is automatic.
Related
Select— When the list is short and static.Command— Underlying primitive (cmdk).CommandPalette— App-wide ⌘K palette (planned).