Combobox
Preview appears after the next Storybook update.
<Combobox />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.
- Inside a Dialog or Drawer: the list opens above the dialog and focus stays in it. Use description for a short second line under each label (12px, muted), like a component's family or a person's role.
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. It sits on the popover layer, above a Dialog, and the dialog keeps focus management: picking returns focus to the input inside the dialog.
Content guidelines
- Placeholder hints at what to type: "Search people", "Type a component name".
- Empty message: say there is no match and what to try next, in one sentence.
- Loading message is a present participle phrase: "Searching people", "Loading repositories".
- Option labels are the names users know (people's display names, component names), not ids.
From the code Generated from the component, not editable here.
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
Motion and interaction
Combobox animates on hover, open and close. Try it in the preview at the top.
| Part | When | What changes | Duration | Easing |
|---|---|---|---|---|
| Trigger | Hover | Background and border | motion.duration.instant75 ms | motion.easing.standardsettles in place |
| Popup | Open | Fade and movement | motion.duration.fast150 ms | motion.easing.enterstarts fast, lands softly |
| Popup | Close | Fade and movement | motion.duration.fast150 ms | motion.easing.exitstarts softly, leaves fast |
With reduced motion turned on, every change happens at once.
From the code Generated from the component, not editable here.
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 |
|---|---|---|---|
optionsrequired | 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. |
portalContainer | HTMLElement | ShadowRoot | RefObject<HTMLElement | ShadowRoot | null> | null | none | Where the popup is rendered. Default: the end of the body, above every layer including a Dialog or Drawer, so the list is never covered. Pass an element (or a ref) to keep it inside a container, for example a shadow root. |
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 (9)
Loads from https://bridge-storybook.floriangouloubi.com (set STORYBOOK_URL at build time). If Storybook is not running, the frames stay empty.
Default
InsideDialog
Grouped
Multiple
WithError
Open
AsyncSearch
Loading
AllStates
From the code Generated from the component, not editable here.
Changelog
- New
The measure now writes the Figma sets drawn under a component's folder as their own entries, so Claude can fix them: popups drawn open (Dialog/Popup, AlertDialog/Popup, Drawer/Popup, Menu/Popup, Popover/Popup, Tooltip/Popup, and Select/Popup or Combobox/Popup, measured open when the stories show them closed) and sets that fix a prop (Pagination/Full, Pagination/Compact, Progress/Linear)
v1.0.0, Nov 11, 2026
- Fix
Component specs count height and width as the content box (a bordered table cell is its height plus its border), the Figma build draws an empty sized slot at its padding instead of 100px, and stories reach every Figma variant: a disabled control on IconButton, Input, Select, Combobox and Textarea, and Figma variant stories for AccordionItem, MenuItem, MenuCheckboxItem, MenuSubmenu and TableEmpty
v1.0.0, Nov 11, 2026
- Fix
Select and Combobox: the highlighted option's corners now follow the popup's corners, like Menu items
v1.0.0, Nov 11, 2026
- Fix
Combobox: a chip cut by the field width shows its full value on hover (title)
v1.0.0, Nov 11, 2026
- Fix
Combobox: the listbox takes the field's name (its
label,aria-labeloraria-labelledby), so it is never an anonymous list, including while it closesv1.0.0, Nov 11, 2026