Combobox
Filters a list of options as you type, for one value or several shown as pills.
Installation
The CLI installs the Radix or Base UI file to match your project, with what it builds on: button, input-group.
If your project doesn't have Wisty UI's theme yet, add it once with npx shadcn@latest add @wisty/cosmos. Until then, components keep shadcn's default look. Installation has the details.
When to use
Use it
- People pick a value from a long list and typing to filter it helps, like a country, a product or an industry.
- People pick several values from such a list. The chips parts show each picked value as a pill in the field.
- The options are in the page, or you fetch them yourself as people type.
Don't use it
- The list is short and people don't need to type. Use Select.
- The value is any text, with no list to pick from. Use Input.
- People pick Salesforce records, with object icons, a meta line, recent records or a search as they type. Use Lookup, which is built on this combobox.
- The list holds actions, like Edit or Delete, rather than values. Use Dropdown menu.
- There are only a few choices and people should see them all. Use Radio group or Checkbox.
Usage
Pass the options to Combobox as items, and render one ComboboxItem per option from a function inside ComboboxList. A function as children needs a client component, so mark the file "use client".
Combobox
├── ComboboxInput
└── ComboboxContent
├── ComboboxEmpty
└── ComboboxList
└── ComboboxItem"use client"
import {
Combobox,
ComboboxContent,
ComboboxEmpty,
ComboboxInput,
ComboboxItem,
ComboboxList,
} from "@/components/ui/combobox"<Combobox items={["Agriculture", "Banking", "Education", "Retail"]}>
<ComboboxInput aria-label="Industry" placeholder="Search industries..." />
<ComboboxContent>
<ComboboxEmpty>No industries found.</ComboboxEmpty>
<ComboboxList>
{(industry: string) => (
<ComboboxItem key={industry} value={industry}>
{industry}
</ComboboxItem>
)}
</ComboboxList>
</ComboboxContent>
</Combobox>ComboboxInput has a chevron button that opens the list; showTrigger={false} removes it. showClear adds a clear button, which takes the chevron's place while the field has a value.
For several values, pass multiple to Combobox and build the field from chips. Call useComboboxAnchor() and give its ref to ComboboxChips as ref and to ComboboxContent as anchor, so the list opens below the whole field, as the Multiple example shows. Each ComboboxChip has a remove button unless you pass showRemove={false}.
Combobox
├── ComboboxChips
│ └── ComboboxValue
│ ├── ComboboxChip
│ └── ComboboxChipsInput
└── ComboboxContent
├── ComboboxEmpty
└── ComboboxList
└── ComboboxItemFor groups, pass items as groups that each hold their own items. The function inside ComboboxList then returns a ComboboxGroup per group, given that group's items, and a function inside its ComboboxCollection returns the options, as the Groups example shows.
ComboboxList
└── ComboboxGroup
├── ComboboxSeparator
├── ComboboxLabel
└── ComboboxCollection
└── ComboboxItemonValueChange gets the new value, or null when the field is cleared. For object items, give Combobox an itemToStringLabel so the field shows a label.
In Radix projects, Combobox is built on @base-ui/react, as shadcn's is, so installing it adds that package beside radix-ui. The two work side by side.
Examples
Groups
Option headers from `ComboboxLabel`, a `ComboboxSeparator`, a disabled option, and a selected value with the `showClear` button.
Multiple
Several values as pills inside the field, set with `multiple` on `Combobox` and a `ComboboxChip` per value.
States
An industry marked with `aria-invalid` above its `FieldError`, and a region with a value and `disabled`.
In a dialog
A loss reason in a modal dialog, where the list opens above the dialog and Escape closes the list before the dialog.
API reference
| Part | Built on |
|---|---|
Combobox | Base UI Combobox.Root (opens in a new tab) |
ComboboxValue | Base UI Combobox.Value (opens in a new tab) |
ComboboxTrigger | Base UI Combobox.Trigger (opens in a new tab) |
ComboboxInput | Base UI Combobox.Input (opens in a new tab) |
ComboboxContent | Base UI Combobox.Popup (opens in a new tab) |
ComboboxList | Base UI Combobox.List (opens in a new tab) |
ComboboxItem | Base UI Combobox.Item (opens in a new tab) |
ComboboxGroup | Base UI Combobox.Group (opens in a new tab) |
ComboboxLabel | Base UI Combobox.GroupLabel (opens in a new tab) |
ComboboxCollection | Base UI Combobox.Collection (opens in a new tab) |
ComboboxEmpty | Base UI Combobox.Empty (opens in a new tab) |
ComboboxSeparator | Base UI Combobox.Separator (opens in a new tab) |
ComboboxChips | Base UI Combobox.Chips (opens in a new tab) |
ComboboxChip | Base UI Combobox.Chip (opens in a new tab) |
ComboboxChipsInput | Base UI Combobox.Input (opens in a new tab) |
ComboboxInput
| Prop | Type | Default | Description |
|---|---|---|---|
showTrigger | boolean | true | |
showClear | boolean | false | |
disabled | boolean | false | Whether the component should ignore user interaction. |
ComboboxContent
| Prop | Type | Default | Description |
|---|---|---|---|
align | "center" | "end" | "start" | "start" | How to align the popup relative to the specified side. |
side | "top" | "bottom" | "left" | "right" | "inline-end" | "inline-start" | "bottom" | Which side of the anchor element to align the popup against. |
sideOffset | number | OffsetFunction | 2 | Distance between the anchor and the popup in pixels. |
alignOffset | number | OffsetFunction | 0 | Additional offset along the alignment axis in pixels. |
anchor | Element | VirtualElement | RefObject<Element | null> | (() => Element | VirtualElement | null) | null | An element to position the popup against. |
ComboboxChip
| Prop | Type | Default |
|---|---|---|
showRemove | boolean | true |
useComboboxAnchorhook
Returns RefObject<HTMLDivElement | null>.
SLDS mapping
Combobox draws SLDS's autocomplete combobox. It runs on Base UI's combobox in Radix projects too, so installing it in a Radix project adds @base-ui/react.
ComboboxInputisslds-combobox__inputinslds-input-has-icon_right, with the chevron as the input icon. Its clear button is SLDS's input icon button.ComboboxContentisslds-dropdown slds-dropdown_fluid slds-dropdown_length-with-icon-7, as Select's list.ComboboxItemisslds-listbox__option slds-listbox__option_plain, with SLDS's check column on the left.ComboboxLabelisslds-listbox__option-header, andComboboxSeparatorisslds-has-divider_top-space.ComboboxEmptyis a plain option row in muted text, since SLDS has no "no results" row.ComboboxChipsis SLDS's inline listbox of pills (slds-has-inline-listbox,slds-listbox_inline), andComboboxChipis aslds-pilloption.
Known gaps:
- SLDS's autocomplete shows a search icon. Here the field has a chevron, which SLDS 2's combobox anatomy also names.
- SLDS 2 shows several values as pills below the field. Here they sit inside it, as in SLDS's inline listbox.
- Option icons, metadata lines, a loading spinner and the 5 and 10 row list heights aren't drawn. For records with icons and meta lines, use Lookup.
| Prop | Value | SLDS | Notes |
|---|---|---|---|
ComboboxInput | default | slds-combobox_container, slds-combobox__input, slds-input-has-icon_right | An input with the chevron button inside, 32px. |
ComboboxInput showClear | true | slds-input__icon_right (clear button) | A 24px icon button replaces the chevron while there's a value. |
ComboboxContent | default | slds-dropdown slds-dropdown_fluid, slds-listbox_vertical | Opens 2px below the field, as wide as it, seven rows tall. |
ComboboxChips | default | slds-has-inline-listbox, slds-listbox_inline | Selected values as pills inside the field. |
ComboboxChip | default | slds-pill (a pill option) | Looks like a pill in a PillGroup. |
Keyboard interactions
| Keys | Action |
|---|---|
| Typing | Filters the options and opens the list. |
| Down Arrow, Up Arrow | Opens the list, or moves the highlight. Opening highlights the selected option, if any. |
| Enter | Selects the highlighted option and closes the list. |
| Escape | Closes the list. On a closed field with a value, clears it. |
| Left Arrow (chips) | From the start of the input, moves to the last chip, then along the chips. Right Arrow returns. |
| Backspace (chips) | In an empty input, removes the last chip. On a focused chip, removes it. |
Inside a Dialog, Sheet, Alert dialog or Popover, the first Escape closes only the list and the next closes the dialog. On a closed field with a value, Escape clears it; in Radix projects the same Escape also closes the dialog.
Accessibility
- Name the input: a
FieldLabelwhosehtmlFormatches the input'sid, oraria-label. - For an error, set
aria-invalidonComboboxInputand pointaria-describedbyat the message, as the States example does. - The chevron, clear and chip remove buttons are out of the Tab order and named "Show options", "Clear" and "Remove". No prop changes those names, so for another language, edit them in your copy of the file.
- While the list is open, everything outside the field and the list is hidden from screen readers, the label and error included. The input keeps its name from the label.
ComboboxEmptyis a status, so screen readers announce it when nothing matches. Always include one.- In Cosmos dark, the invalid field's text reaches about 4.3:1, below WCAG AA's 4.5:1. Salesforce's own stylesheet fails the same way.
Responsive behavior
- The field fills its container. The list is as wide as the field, shows seven rows, then scrolls, and opens above the field when there's no room below.
- Chips wrap onto more rows as the field fills, and the input keeps at least 4rem.
- Long option labels wrap in the list. A long value in the field scrolls with the text.
Performance
- Filtering runs over
itemson every keystroke. A few hundred items are fine. - For thousands, or for records on a server, filter yourself: pass
filteredItemsor controlinputValue, and fetch as people type.
Rules
- Disable with
disabledon bothComboboxandComboboxInput, as the States example does. - Don't add your own Escape or outside-click handling inside a dialog. The dialogs already leave Escape to an open list.
Related
- SelectPicks one value from a short list in a dropdown, as a picklist does.
- LookupPicks records from a search, one in the field or several as pills.
- Dropdown menuOpens a menu of actions or options from a button, like a record's row actions.
- Radio groupPicks exactly one option from a short list that stays visible.