Combobox
A searchable selection component that combines a text input with a dropdown list, allowing users to filter and select from available options
pnpm add @wandercom/design-system-web
import {
Combobox,
ComboboxContent,
ComboboxEmpty,
ComboboxInput,
ComboboxItem,
ComboboxList,
} from '@wandercom/design-system-web/ui/combobox';
const options = ['Option 1', 'Option 2', 'Option 3'];
export function Example() {
return (
<Combobox items={options}>
<ComboboxInput placeholder="Search..." />
<ComboboxContent>
<ComboboxEmpty>No results found.</ComboboxEmpty>
<ComboboxList>
{(item) => (
<ComboboxItem key={item} value={item}>
{item}
</ComboboxItem>
)}
</ComboboxList>
</ComboboxContent>
</Combobox>
);
}The default example shows a basic combobox with type-to-filter, a variant with the clear button enabled, and the disabled state.
const destinations = ['Aspen', 'Big Bear', 'Joshua Tree', 'Lake Tahoe', 'Malibu'];
<Combobox items={destinations}>
<ComboboxInput placeholder="Search destinations..." />
<ComboboxContent>
<ComboboxEmpty>No destinations found.</ComboboxEmpty>
<ComboboxList>
{(item) => (
<ComboboxItem key={item} value={item}>
{item}
</ComboboxItem>
)}
</ComboboxList>
</ComboboxContent>
</Combobox>
<Combobox items={destinations}>
<ComboboxInput placeholder="With clear button" showClear />
<ComboboxContent>
<ComboboxEmpty>No destinations found.</ComboboxEmpty>
<ComboboxList>
{(item) => (
<ComboboxItem key={item} value={item}>
{item}
</ComboboxItem>
)}
</ComboboxList>
</ComboboxContent>
</Combobox>
<Combobox disabled items={destinations}>
<ComboboxInput placeholder="Disabled combobox" />
<ComboboxContent>
<ComboboxList>
{(item) => (
<ComboboxItem key={item} value={item}>
{item}
</ComboboxItem>
)}
</ComboboxList>
</ComboboxContent>
</Combobox>When item values are objects with a { value, label } shape, Base UI automatically uses label for display and filtering, while value is used for form submission. This is useful when the display text differs from the underlying ID.
const destinations = [
{ value: 'aspen-co', label: 'Aspen, Colorado' },
{ value: 'big-bear-ca', label: 'Big Bear, California' },
{ value: 'joshua-tree-ca', label: 'Joshua Tree, California' },
];
<Combobox items={destinations}>
<ComboboxInput placeholder="Search destinations..." />
<ComboboxContent>
<ComboboxEmpty>No destinations found.</ComboboxEmpty>
<ComboboxList>
{(item) => (
<ComboboxItem key={item.value} value={item}>
{item.label}
</ComboboxItem>
)}
</ComboboxList>
</ComboboxContent>
</Combobox>When managing the selected value externally, pass both value/onValueChange and inputValue/onInputValueChange. Base UI updates the input text via onInputValueChange when an item is selected, so both callbacks are required for the input to reflect the selection.
const [value, setValue] = useState<Destination | null>(null);
const [inputValue, setInputValue] = useState('');
<Combobox
inputValue={inputValue}
items={destinations}
value={value}
onInputValueChange={setInputValue}
onValueChange={setValue}
>
<ComboboxInput placeholder="Search destinations..." />
<ComboboxContent>
<ComboboxEmpty>No destinations found.</ComboboxEmpty>
<ComboboxList>
{(item) => (
<ComboboxItem key={item.value} value={item}>
{item.label}
</ComboboxItem>
)}
</ComboboxList>
</ComboboxContent>
</Combobox>Long lists scroll inside the popup, which is capped at 500px (or the available viewport height, whichever is smaller). Chevron indicators appear over the list edges while more content is available in that direction. Pass scrollable={false} to ComboboxContent to remove the 500px cap and the indicators — the popup then grows to the full available height and the list still scrolls if it runs out of room.
const countries = ['Argentina', 'Australia', 'Austria' /* ... 30+ items */];
<Combobox items={countries}>
<ComboboxInput placeholder="Search countries..." />
<ComboboxContent>
<ComboboxEmpty>No countries found.</ComboboxEmpty>
<ComboboxList>
{(item) => (
<ComboboxItem key={item} value={item}>
{item}
</ComboboxItem>
)}
</ComboboxList>
</ComboboxContent>
</Combobox>
<Combobox items={countries}>
<ComboboxInput placeholder="Search countries..." />
<ComboboxContent scrollable={false}>
<ComboboxEmpty>No countries found.</ComboboxEmpty>
<ComboboxList>
{(item) => (
<ComboboxItem key={item} value={item}>
{item}
</ComboboxItem>
)}
</ComboboxList>
</ComboboxContent>
</Combobox>By default an item grows to fit its label, wrapping long text onto multiple lines. Pass truncate to ComboboxItem to pin the row to a single line and ellipsize overflowing text instead. Truncation is intended for plain text labels — labels that combine icons and text should use the default growing rows.
const properties = [
'Aspen Chalet',
'Big Bear Lakefront Lodge with Private Dock and Mountain Views',
/* ... */
];
// Default: rows grow and long labels wrap
<Combobox items={properties}>
<ComboboxInput placeholder="Search properties..." />
<ComboboxContent>
<ComboboxEmpty>No properties found.</ComboboxEmpty>
<ComboboxList>
{(item) => (
<ComboboxItem key={item} value={item}>
{item}
</ComboboxItem>
)}
</ComboboxList>
</ComboboxContent>
</Combobox>
// truncate: rows stay single-line and ellipsize
<Combobox items={properties}>
<ComboboxInput placeholder="Search properties..." />
<ComboboxContent>
<ComboboxEmpty>No properties found.</ComboboxEmpty>
<ComboboxList>
{(item) => (
<ComboboxItem key={item} truncate value={item}>
{item}
</ComboboxItem>
)}
</ComboboxList>
</ComboboxContent>
</Combobox>Use ComboboxGroup, ComboboxLabel, and ComboboxSeparator to organize options into labeled sections.
const regionGroups = [
{ value: 'Mountains', items: ['Aspen', 'Big Bear', 'Telluride'] },
{ value: 'Desert', items: ['Joshua Tree', 'Palm Springs', 'Sedona'] },
{ value: 'Coast', items: ['Malibu', 'Carmel', 'Cannon Beach'] },
];
<Combobox items={regionGroups}>
<ComboboxInput placeholder="Search destinations..." />
<ComboboxContent>
<ComboboxEmpty>No destinations found.</ComboboxEmpty>
<ComboboxList>
{(group) => (
<ComboboxGroup items={group.items} key={group.value}>
<ComboboxLabel>{group.value}</ComboboxLabel>
<ComboboxCollection>
{(item) => (
<ComboboxItem key={item} value={item}>
{item}
</ComboboxItem>
)}
</ComboboxCollection>
</ComboboxGroup>
)}
</ComboboxList>
</ComboboxContent>
</Combobox>items:
value?:
defaultValue?:
onValueChange?:
inputValue?:
onInputValueChange?:
disabled?:
multiple?:
open?:
onOpenChange?:
placeholder?:
showTrigger?:
showClear?:
size?:
triggerLabel?:
clearLabel?:
disabled?:
className?:
side?:
sideOffset?:
align?:
alignOffset?:
anchor?:
scrollable?:
className?:
value:
truncate?:
disabled?:
className?:
className?:
className?:
className?:
className?:
showRemove?:
removeLabel?:
className?:
The Combobox component is built on the Base UI Combobox primitive, which implements the WAI-ARIA combobox pattern for accessible searchable selection.
- Supports keyboard interaction: type to filter options, arrow keys to navigate the list,
Enterto select the highlighted item, andEscapeto close the popup - Screen reader announces the count of filtered results as the user types
- Focus management ensures visible focus indicators during keyboard navigation
- Disabled items apply
data-disabledand are excluded from keyboard navigation - Multi-select mode uses chips with accessible remove buttons for deselecting values
- Icon-only buttons ship with default English labels — "Open popup" on the trigger, "Clear selection" on the clear button, and "Remove" on chip remove buttons. Override them for localization via
triggerLabel/clearLabelonComboboxInput,aria-labelonComboboxTrigger/ComboboxClear, andremoveLabelonComboboxChip. The empty-state message defaults to "No items found." and is customized viaComboboxEmptychildren