Combobox
Headless editable combobox primitives for filtering suggestions and selecting a value from a listbox.
Combobox in motion
When to Use
Use Combobox when users should type to find an option, such as choosing a city
from a long list. Use Select when users must choose from a fixed list without
typing. Use Listbox when the choices should remain visible and no text input is
needed. Enable freeSolo only when typed values outside the option list are
valid.
Features
The behavior Atom owns before your product adds appearance.
- Controls selection, input text, and open state independently.
- Filters, groups, highlights, and selects options.
- Provides a native disclosure Trigger and sizes Content from the full Control.
- Supports free-form values, loading, empty state, clearing, and disabled items.
- Positions Content with Floating UI and portals on request.
- Commits outside dismissal on a completed activation, rejects cancelled or dragged pointer sessions, and supports consumer cancellation.
- Provides generated combobox/listbox ARIA relationships.
- Submits the selected value through a hidden named input.
- Exposes reusable option filtering, labeling, grouping, and navigation helpers.
Import
Anatomy
API Reference
Root
Owns all combobox state and renders no visible wrapper. When name is set, it
adds an accessibility-hidden input containing the selected value.
| Prop | Type | Default |
|---|---|---|
options | ComboboxOption[] | required |
value | string | null | - |
defaultValue | string | null | null |
onValueChange | (value: string | null) => void | - |
inputValue | string | - |
defaultInputValue | string | Selected option label or "" |
onInputValueChange | (value: string) => void | - |
open | boolean | - |
defaultOpen | boolean | false |
onOpenChange | (open: boolean) => void | - |
filterOptions | ComboboxFilter | filterComboboxOptions |
groupBy | (option) => string | - |
freeSolo | boolean | false |
clearOnSelect | boolean | false |
openOnFocus | boolean | true |
loading | boolean | false |
noOptionsText | ReactNode | "No options" |
loadingText | ReactNode | "Loading" |
disabled | boolean | false |
readOnly | boolean | false |
required | boolean | false |
invalid | boolean | false |
validationBehavior | "inline" | "native" | Field/Form value or "native" |
name | string | - |
form | string | - |
The transparent native value proxy receives name, form, the committed
value, disabled state, and required state. It aligns to the visible Input and
redirects browser validation focus there, so typed display text does not
satisfy required validity until a value is committed. The visible Input
receives Field state, the generated control ID, Field label and description
relationships, and external form association. Uncontrolled value,
input text, and open state return to their defaults on native form reset.
When composed inside Field.Root, Combobox inherits disabled, read-only,
required, invalid, validation behavior, label, description, and error
relationships. It also works standalone through the equivalent Root props.
Fieldset.Root and Form.Root aggregate native/inline validity through the
shared Field and form validation contracts.
Label
Labels Input when used outside Group and labels the option group when nested
inside Group. It renders label outside a group and div inside one.
| Prop | Type | Default |
|---|---|---|
asChild | boolean | false |
render | RenderProp | - |
| Data attribute | Values |
|---|---|
[data-slot] | "combobox-label" |
Input
Renders the editable input, controls the filter text, opens Content, and owns all combobox keyboard interaction.
| Prop | Type | Default |
|---|---|---|
asChild | boolean | false |
render | RenderProp | - |
autoComplete | string | "off" |
| ARIA attribute | Values |
|---|---|
role | "combobox" |
aria-expanded | Current open state |
aria-haspopup | "listbox" |
aria-controls | Generated Listbox ID |
aria-activedescendant | Highlighted Item ID while open |
aria-autocomplete | "list" |
aria-required | "true" when required |
aria-invalid | "true" when invalid |
| Data attribute | Values |
|---|---|
[data-slot] | "combobox-input" |
[data-state] | "open" | "closed" |
[data-disabled] | Present when disabled |
[data-readonly] | Present when read only |
[data-invalid] | Present when invalid |
Control
Wraps Input, Clear, and Trigger as the complete visible control. Content uses this element as its positioning reference and minimum inline size.
| Data attribute | Values |
|---|---|
[data-slot] | "combobox-control" |
[data-state] | "open" | "closed" |
[data-disabled] | Present when disabled |
[data-readonly] | Present when read only |
[data-required] | Present when required |
[data-invalid] | Present when invalid |
Clear
Clears the selected value and input, then returns focus to Input. It renders a button and is hidden and disabled when no selected value exists or Root is disabled/read-only.
| Prop | Type | Default |
|---|---|---|
asChild | boolean | false |
render | RenderProp | - |
| ARIA attribute | Values |
|---|---|
aria-label | Consumer value or "Clear selection" |
aria-hidden | "true" while hidden |
| Data attribute | Values |
|---|---|
[data-slot] | "combobox-clear" |
[data-hidden] | Present while hidden |
Trigger
Renders the button that toggles Content and returns focus to Input. Native focus scrolling remains enabled so a mobile browser can reveal Input when opening its virtual keyboard. Trigger inherits disabled/read-only behavior from Root or Field.
| ARIA attribute | Values |
|---|---|
aria-expanded | Current open state |
aria-haspopup | "listbox" |
aria-controls | Generated Listbox ID |
| Data attribute | Values |
|---|---|
[data-slot] | "combobox-trigger" |
[data-state] | "open" | "closed" |
[data-invalid] | Present when invalid |
Portal
Moves its children to document.body by default without a wrapper.
| Prop | Type | Default |
|---|---|---|
container | Element | DocumentFragment | null | document.body |
disabled | boolean | false |
Content
Renders and positions the open popup below Control with flip and viewport-shift collision handling. It owns no listbox role; place Listbox inside it.
| Prop | Type | Default |
|---|---|---|
sideOffset | number | 4 |
onInteractOutside | (event: OutsideInteractionEvent) => void | - |
onInteractOutside runs before dismissal. Calling its preventDefault()
method keeps Content open without cancelling the original destination click.
The event reports pointerType as "mouse", "touch", "pen", or
"virtual".
| Data attribute | Values |
|---|---|
[data-slot] | "combobox-content" |
[data-state] | "open" |
[data-positioned] | Present after positioning |
Listbox
Contains the available options and receives the ID referenced by Input.
| ARIA attribute | Values |
|---|---|
role | "listbox" |
| Data attribute | Values |
|---|---|
[data-slot] | "combobox-listbox" |
[data-state] | "open" | "closed" |
Group
Groups related options. A nested Label supplies its generated accessible name;
native aria-label or aria-labelledby overrides that relationship.
| ARIA attribute | Values |
|---|---|
role | "group" |
aria-label | Native label when supplied |
aria-labelledby | Consumer ID or nested Label ID |
| Data attribute | Values |
|---|---|
[data-slot] | "combobox-group" |
Item
Represents one selectable option, registers its value in list order, and hides itself when its matching Root option is excluded by the active filter.
| Prop | Type | Default |
|---|---|---|
value | string | required |
label | string | Value or matching option label |
disabled | boolean | false |
| ARIA attribute | Values |
|---|---|
role | "option" |
aria-selected | Current selected state |
aria-disabled | "true" when disabled |
| Data attribute | Values |
|---|---|
[data-slot] | "combobox-item" |
[data-state] | "checked" | "unchecked" |
[data-highlighted] | Present when highlighted |
[data-value] | Item value |
[data-disabled] | Present when disabled |
Empty
Renders only when filtering produces no options and Root is not loading. Its
children default to noOptionsText.
| Data attribute | Values |
|---|---|
[data-slot] | "combobox-empty" |
Loading
Renders only while Root is loading. Its children default to loadingText.
| Data attribute | Values |
|---|---|
[data-slot] | "combobox-loading" |
useComboboxContext
Returns the current state and actions for advanced custom parts, including
filteredOptions and groupedOptions. It must be called below Root. Prefer the
namespaced parts for standard input, listbox, and option behavior.
filterComboboxOptions
Returns options whose case-insensitive display label contains the input text. An empty input returns the original options.
getComboboxOptionLabel
Returns option.label when present, otherwise option.value.
groupComboboxOptions
Groups options in encounter order using groupBy. Without groupBy, it
returns one group with a null label.
getNextComboboxValue
Returns the next or previous value with wrapping. It returns null for an empty
array and begins at the first/last value when the current value is absent.
Examples
Choose a City
Free-Form Controlled Value
Accessibility
The aligned committed-value input owns required validity. A validation attempt is mirrored to the visible Input and Field. Inline behavior suppresses the browser bubble; native behavior keeps it and redirects focus to Input.
Combobox follows the
WAI-ARIA Editable Combobox pattern.
Provide an accessible Label, render every available value as an Item, and keep
disabled options marked with disabled.
| Key | Description |
|---|---|
ArrowDown | Opens and highlights the next enabled Item, wrapping at the end. |
ArrowUp | Opens and highlights the previous enabled Item, wrapping at the start. |
Home | Highlights the first enabled Item. |
End | Highlights the last enabled Item. |
Enter | Selects the highlighted Item, or commits free-form text when enabled. |
Escape | Closes an open popup; when already closed, clears selection and input. |
Tab | Closes the popup and continues normal focus movement. |
Changelog
Unreleased
0.24.0
- Added public Agent Knowledge for component selection, required composition, recurring mistakes, and validation.
0.20.2
- Allowed native focus scrolling when Trigger returns focus to Input, so mobile browsers can reveal the control above the virtual keyboard.
0.20.0
- Added preventable
Content.onInteractOutsideand moved outside dismissal to the shared layer-aware completed-activation contract.
0.18.2
- Added Control and Trigger parts, anchored Content to the full Control, and exposed inherited form state on the Control styling surface.
- Made authored Items filter themselves from Root options and initialized
display text from
defaultValuewhendefaultInputValueis omitted.
0.18.1
- Made touch and pen outside dismissal wait for a completed tap and cancel on movement, scrolling, or pointer cancellation.
0.6.16
- Explicitly scrolled inline validation-directed focus into view.
0.6.15
- Exposed inline validation-directed focus through
[data-focus-visible]until blur.
0.6.13
- Mirrored committed-value proxy validity to the visible Combobox, Field, and Form under the shared inline/native validation contract.
0.6.12
- Moved native required validity to an aligned proxy holding the committed logical value, so display text alone no longer satisfies selection validity.
0.5.0
- Added Field state, generated input ID, label, and description integration plus external-form and uncontrolled reset behavior.
0.2.0
- Fixed option selection so pointer clicks, including already-selected options,
close the listbox consistently, and
clearOnSelectalso applies to free-solo Enter commits. - Fixed
openOnFocusso empty states can open on focus whenCombobox.Emptyis mounted. - Added shared dismissable layer Escape handling so Combobox closes before parent overlays when nested inside Dialog, Drawer, Modal, or Popover.
- Fixed outside pointer dismissal so Combobox closes reliably when clicking outside the input or content during inspection-heavy renders.
0.1.0
- Initial Atom release.