Combobox
A filterable select with chips, groups and async results, in a popup that resizes as you type.
pnpm dlx shadcn@latest add https://hextaui.com/r/combobox.jsonAdds the component, the HextaUI theme tokens and any HextaUI components it depends on.
Add the theme tokens to your global CSS, if you haven’t yet.
Install the dependencies.
pnpm add @base-ui/react @tabler/icons-react class-variance-authority cnCopy and paste the following code into your project.
components/ui/combobox.tsx Update the import paths to match your project setup.
Pass the options to items and render each one with a function inside <ComboboxList />. The combobox filters them as you type and only renders the matches. Objects work too: their label is shown in the input and their value is submitted.
Type in the field to filter the list.
A button shows the value and the search field moves into the popup.
With multiple, each selected item becomes a chip before the input.
Clear button
showClear adds a clear button that takes the chevron’s place while there is a value, so the field never grows.
With icons
Icons inside an item are sized and muted for you. autoHighlight highlights the first match while typing, so Enter picks it.
Groups and separators
Pass groups shaped like { value, items } and render each with <ComboboxGroup />, <ComboboxLabel /> and <ComboboxCollection />. Empty groups hide while filtering.
Multiple
With multiple, selections become chips inside <ComboboxChips />. The popup stays open while you pick, Backspace in the empty input removes the last chip, and the arrow keys move between chips.
Search inside the popup
Use <ComboboxTrigger /> for a select-like field. Put the input inside <ComboboxContent /> and it becomes a search box with an icon, and the popup widens to at least 15rem.
Trigger rendered as a Button
Pass render to the trigger to use any button. The popup anchors to it and keeps at least its width.
Controlled
Control the selection with value and onValueChange, and the popup with open and onOpenChange. Clearing sets the value to null.
Value: Peach
Disabled, invalid and disabled items
disabled on the root dims the field and its buttons. aria-invalid on the input draws the error ring. Disabled items are skipped by the arrow keys.
Items starting with B are disabled.
Long content and large lists
Long and unbroken labels wrap instead of widening the popup. limit caps how many matches render, which keeps a 500 item list fast.
Shows the first 100 matches.
Async search
Turn off built-in filtering with filter={null}, fetch on onInputValueChange, and show progress in <ComboboxStatus />, which announces it to screen readers. The popup height animates as results change.
Inside a sheet
The popup layers above the sheet, and Escape closes the popup before the sheet.
Right to left
The popup picks up the field’s direction, so the clear button, chips and items mirror without extra props.
| Key | Action |
|---|---|
| ↓↑ | Opens the popup and moves the highlight through the matches. Disabled items are skipped. |
| Enter | Picks the highlighted item. With nothing highlighted it closes the popup and lets the form submit. |
| Escape | Closes the popup. When it is already closed, clears the value and the input. |
| HomeEnd | Moves the text cursor to the start or end of the input. |
| Backspace | In an empty chips input, removes the last chip. On a focused chip, removes it. |
| ←→ | With chips, moves focus between chips and back to the input. Mirrored in right-to-left layouts. |
| Tab | Closes the popup and moves focus on. |
- Give the input a visible
<label>throughidandhtmlFor, or anaria-label. A<ComboboxTrigger />without visible text needs anaria-labeltoo. - The chevron button is labelled “Show options”, the clear button “Clear selection” and each chip’s remove button “Remove”.
- Highlighting moves with
aria-activedescendant, so focus stays in the input while you browse. - Inputs use a 16px font on touch screens so iOS doesn’t zoom in, and items grow to a 44px tap target.
Built on the Base UI combobox. Every part accepts the props of the primitive it wraps; the tables list the ones you’ll use most.
| Prop | Type | Default |
|---|---|---|
itemsThe options. Filtered as you type and passed to the list’s render function. | Item[] | Group[] | – |
multipleSelect several values, shown as chips. | boolean | false |
value | Value | Value[] | null | – |
defaultValue | Value | Value[] | null | – |
onValueChange | (value, details) => void | – |
open | boolean | – |
defaultOpen | boolean | false |
onOpenChange | (open: boolean, details) => void | – |
inputValue | string | – |
defaultInputValue | string | – |
onInputValueChange | (inputValue: string, details) => void | – |
filterCustom matching. null turns filtering off for server-side search. | ((item, query, itemToString) => boolean) | null | – |
limitMaximum number of matches to render. -1 means all. | number | -1 |
autoHighlightHighlight the first match while typing. | boolean | false |
highlightItemOnHover | boolean | true |
openOnInputClick | boolean | true |
loopFocusWrap the highlight from the last item to the first. | boolean | true |
itemToStringLabelText shown in the input for an object item. | (item) => string | – |
itemToStringValueValue submitted with the form for an object item. | (item) => string | – |
isItemEqualToValue | (item, value) => boolean | – |
name | string | – |
required | boolean | false |
disabled | boolean | false |
readOnly | boolean | false |
modalLock page scroll and outside clicks while open. | boolean | false |
virtualizedSet when rendering items with a virtualizer. | boolean | false |
localeLocale used for matching. | Intl.LocalesArgument | – |
Outside the popup it renders the full field. Inside <ComboboxContent /> it becomes a compact search box.
| Prop | Type | Default |
|---|---|---|
showTriggerShow the chevron button. Always off inside the popup unless set. | boolean | true outside the popup |
showClearShow a clear button in the chevron’s place while there is a value. | boolean | false |
classNameApplied to the input group around the input. | string | – |
disabled | boolean | false |
placeholder | string | – |
| Attribute | Description |
|---|---|
data-slot="combobox-input-group" | The field around the input. |
data-slot="combobox-input" | The text input. |
data-slot="combobox-input-actions" | Holds the chevron and clear buttons in one stacked cell. |
data-popup-open | Present on the input while the popup is open. |
data-popup-side | The side the popup opened on. |
data-list-empty | Present when nothing matches. |
data-disabled | Present when disabled. |
data-invalid | Present when invalid inside a Base UI Field. |
| Prop | Type | Default |
|---|---|---|
childrenUsually a <ComboboxValue />. The chevron is added after it. | ReactNode | – |
renderWhen set, the built-in field styles are skipped. | ReactElement | (props, state) => ReactElement | <button> |
| Attribute | Description |
|---|---|
data-slot="combobox-trigger" | The trigger button. |
data-slot="combobox-trigger-value" | Wraps the truncated value. |
data-slot="combobox-trigger-icon" | The chevron. Flips while open. |
data-popup-open | Present while the popup is open. |
data-placeholder | Present while no value is selected. |
| Prop | Type | Default |
|---|---|---|
childrenRender the selected value yourself, for example as chips. | ReactNode | (value) => ReactNode | – |
placeholderShown while nothing is selected. | ReactNode | – |
| Prop | Type | Default |
|---|---|---|
side | "top" | "bottom" | "left" | "right" | "inline-start" | "inline-end" | "bottom" |
align | "start" | "center" | "end" | "start" |
sideOffset | number | 6 |
alignOffset | number | 0 |
anchorPosition against another element. Defaults to the field. See useComboboxAnchor. | Element | RefObject<Element | null> | VirtualElement | (() => Element | VirtualElement | null) | null | – |
dirDefaults to the field’s direction. | "ltr" | "rtl" | – |
| Attribute | Description |
|---|---|
data-slot="combobox-positioner" | Positions the popup. |
data-slot="combobox-content" | The popup surface. |
data-slot="combobox-content-sizer" | Measured to animate the popup’s height as matches change. |
data-open | Present while open. |
data-side | The side it opened on. |
data-align | Its alignment. |
data-empty | Present when nothing matches. |
data-starting-style | Present while animating in. |
data-ending-style | Present while animating out. |
--combobox-item-radius | Item radius, derived from the popup radius minus its padding. |
| Prop | Type | Default |
|---|---|---|
childrenCalled for every match of items. | ReactNode | (item, index) => ReactNode | – |
| Attribute | Description |
|---|---|
data-slot="combobox-list" | The scrolling list. |
| Prop | Type | Default |
|---|---|---|
valueThe item this row represents. | Item | – |
disabled | boolean | false |
render | ReactElement | (props, state) => ReactElement | <div> |
| Attribute | Description |
|---|---|
data-slot="combobox-item" | An option. |
data-slot="combobox-item-indicator" | The check, scaled in when selected. |
data-highlighted | Present while highlighted. |
data-selected | Present when selected. |
data-disabled | Present when disabled. |
| Prop | Type | Default |
|---|---|---|
itemsOn ComboboxGroup: the group’s own items. | Item[] | – |
childrenOn ComboboxCollection: renders each match. | (item, index) => ReactNode | – |
| Attribute | Description |
|---|---|
data-slot="combobox-group" | A group of items. |
data-slot="combobox-label" | The group heading. |
<ComboboxEmpty /> shows its children only when nothing matches. <ComboboxStatus /> is a live region for loading and result messages. Both collapse to nothing when empty.
| Attribute | Description |
|---|---|
data-slot="combobox-empty" | The no-results message. |
data-slot="combobox-status" | The live status message. |
data-slot="combobox-separator" | A divider between groups. |
| Prop | Type | Default |
|---|---|---|
children | ReactNode | <IconX /> |
| Attribute | Description |
|---|---|
data-slot="combobox-clear" | Labelled “Clear selection”. |
data-visible | Present while there is something to clear. |
| Prop | Type | Default |
|---|---|---|
classNameApplied to the field that wraps the chips. | string | – |
| Attribute | Description |
|---|---|
data-slot="combobox-chips" | The field that holds chips and input. |
| Prop | Type | Default |
|---|---|---|
showRemoveShow the remove button. | boolean | true |
| Attribute | Description |
|---|---|
data-slot="combobox-chip" | A selected value. |
data-slot="combobox-chip-label" | Its truncated label. |
data-slot="combobox-chip-remove" | Labelled “Remove”. |
The text input that sits after the chips. Accepts the same props as the Base UI input.
| Attribute | Description |
|---|---|
data-slot="combobox-chips-input" | The chips input. |
useComboboxAnchor()returns a ref to pass to an element and toanchoron the content.useComboboxFilter()returns locale-awarecontains,startsWithandendsWithmatchers forfilter.useComboboxFilteredItems()reads the current matches, for counts or virtualized lists.createComboboxItems(data, { getValue })builds an item collection whose selection value is a primitive id, like a database key, instead of the whole object.comboboxFieldVariantsexposes the field styles for building custom fields.