Combobox
StableA text input that filters a list of options as the user types, then selects one or several of them. Built for long lists and async search (people, repositories, tokens).
When to use
Use it for
- Choosing from a long or growing list where typing is faster than scrolling: reviewers, repositories, components, tokens.
- Results come from a server: control inputValue, fetch, pass the results as options with filter={null} and loading while waiting.
- Several values from a long list (multiple): selected values become removable chips inside the field.
Not for
- Short known lists (under about ten): use Select, which needs no typing.
- Free text with suggestions (a search box): the value must be one of the options here, so use Input or an autocomplete pattern.
- Two or three options: use RadioGroup.
Do and don't
Every example is rendered with the real component, so it always matches what ships.
Add a second line when names are ambiguous
A role or an email under the name helps pick the right Ada, and is matched by screen readers too.
Write an empty message that says what to try
"No one matches. Check the spelling or invite them." turns a dead end into a next step.
Spinner with no words
While loading, say what is happening ("Searching people"). The message is also announced to screen readers.
Combobox for a handful of fixed options
Typing to find one of three themes is slower than seeing them. Use Select or RadioGroup.
Accessibility
- Base UI Combobox: the input has role combobox with aria-expanded, aria-controls and aria-activedescendant; the popup is a listbox.
- Keyboard: typing filters; Down Arrow opens and moves; Enter selects; Escape closes, then clears; with multiple, Left Arrow from the start of the input moves through the chips and Backspace removes one.
- loading renders the message in a live status region and sets aria-busy on the popup; the empty message is announced the same way.
- The clear and toggle buttons inside the field have accessible names ("Clear selection", "Show options"); each chip remove button says "Remove <name>".
- The Field label names the input; hint and error are linked through aria-describedby.
- The popup is portaled and re-applies the theme, brand and density of its field.
Anatomy and tokens
Read from packages/react/src/components/Combobox/Combobox.module.css at build time. Every value is a token: open one to see its
alias chain in both themes.
- Field box holding prefix, chips, input and actions
.control - sm
.sm - lg
.lg - Field box holding prefix, chips, input and actions, hover
@media (hover: hover) .control:hover:not([data-disabled], [data-invalid], :has([aria-invalid="true"])) - Field box holding prefix, chips, input and actions
.control:focus-within - Field box holding prefix, chips, input and actions
.control[data-invalid], .control:has(input[aria-invalid="true"]) - Field box holding prefix, chips, input and actions
.control[data-invalid]:focus-within, .control:has(input[aria-invalid="true"]):focus-within - Field box holding prefix, chips, input and actions
.control[data-disabled] - Field box holding prefix, chips, input and actions
.control[data-readonly] - Text input (combobox)
.input - Text input (combobox), placeholder
.input::placeholder - Prefix icon
.affix - Clear and toggle buttons
.iconAction - Clear and toggle buttons, hover
@media (hover: hover) .iconAction:hover:not([data-disabled]) - Clear and toggle buttons, focus
.iconAction:focus-visible - Clear and toggle buttons
.iconAction[data-disabled] - Toggle button (chevron)
.trigger > * - multiple
.multiple - Chip row (multiple)
.chips - multiple
.multiple .input - Selected value chip
.chip - Selected value chip, focus
.chip:focus-visible, .chip[data-highlighted] - Chip remove button
.chipRemove - Chip remove button, hover
@media (hover: hover) .chipRemove:hover - positioner
.positioner - Popup surface (popup.* tokens)
.popup - Popup surface (popup.* tokens)
.popup[data-ending-style] - Scrollable list
.list - Option
.item - Option
.item[data-highlighted] - Option
.item[data-selected] .itemText - Option
.item[data-disabled] - Option description
.itemDescription - Option
.item[data-disabled] .itemDescription - Check mark on selected options
.indicator - Group heading
.groupLabel - separator
.separator - Loading status row
.status, .empty - Field box holding prefix, chips, input and actions
@media (pointer: coarse) .control
Props
Parsed from the TypeScript types in packages/react/src/components/Combobox/Combobox.tsx, so this table cannot drift from the
code.
It also accepts every native <input> attribute except value, defaultValue, onChange, size, prefix, children.
| Prop | Type | Default | Description |
|---|---|---|---|
options required | ComboboxOption[] | none | The options to filter. For async search, pass the latest results and set `filter={null}`. |
multiple | boolean | false | Pick several values, shown as removable chips. |
value | ComboboxValue | none | Selected value (controlled): a string, an array with `multiple`, or null. |
defaultValue | ComboboxValue | none | Initially selected value (uncontrolled). |
onValueChange | (value: ComboboxValue) => void | none | Called with the new value (string, string[] with `multiple`, or null). |
inputValue | string | none | Text in the input (controlled). Use it to drive an async search. |
defaultInputValue | string | none | No description in the code yet. |
onInputValueChange | (inputValue: string) => void | none | Called on every keystroke with the query. |
filter | ((option: ComboboxOption, query: string) => boolean) | null | none | Custom matcher, or `null` to show `options` as given (already filtered by a server). Default: case and accent insensitive "contains". |
loading | boolean | false | Results are loading: shows a spinner row, sets aria-busy and announces `loadingMessage`. |
loadingMessage | string | 'Loading results' | Announced and shown while loading. Default "Loading results". |
emptyMessage | ReactNode | 'No matches. Try another name.' | Shown when no option matches. Say what to try next. |
label | string | none | Visible label. When set, the Combobox wraps itself in a Field. Leave unset inside a Field. |
hint | ReactNode | none | Help under the label (Field description). Only with `label`. |
error | ReactNode | none | Error message. Only with `label`. |
hideLabel | boolean | none | No description in the code yet. |
size | 'sm' | 'md' | 'lg' | 'md' | Height follows the density tokens. Default `md`. |
prefix | ReactNode | none | Content before the query, usually a search Icon. |
open | boolean | none | Popup open state (controlled). |
defaultOpen | boolean | none | No description in the code yet. |
onOpenChange | (open: boolean) => void | none | No description in the code yet. |
Playground
Change the props and see the real Combobox update. Controls are generated from the TypeScript props; nothing here changes the system.
<Combobox label="Reviewer" placeholder="Search people" />Workbench
For contributors: every state with the accessibility panel, in the component workbench (Storybook). The stories double as visual test fixtures; the guidance on this page is the user documentation (open Combobox in Storybook) .
Embed Storybook stories here (8)
Loads from https://bridge-storybook.floriangouloubi.com (set STORYBOOK_URL at build time). If Storybook is not running, the frames stay
empty.
Default
Grouped
Multiple
WithError
Open
AsyncSearch
Loading
AllStates
Changelog
- New
Forms: Field (label, description, error wired by Base UI Field) and Input rebuilt on it (prefix, suffix, sizes; label, hint and error props kept). New Textarea (autosize, character count), Select (groups, descriptions), Combobox (filtering, async loading and empty states, multiple with chips), Checkbox (indeterminate, parent), CheckboxGroup, RadioGroup, Switch, Slider (single and range), NumberField (steppers, units) and Form (server errors with a focusable error summary, FormActions footer).
Unreleased (current version 0.1.0, no release tagged yet) , Oct 17, 2026