Combobox
Combobox combines a text input with a list of options that users are able to select from.
Import#
import { Combobox } from '@volue/wave-react';// you may also access Combobox context via hook:// import { useComboboxContext } from '@volue/wave-react';
Combobox is a compound component that consists of multiple parts:
-
Combobox.Root: The wrapper that holds the data (items), owns the selection state, and provides context to its children. -
Combobox.TextInput: A text input that filters the list as the user types. It can be the field itself (typeahead) or a search field rendered inside the popup. -
Combobox.Trigger: A button-style trigger that displays the current selection and opens the popup — pair it with aCombobox.TextInputplaced insideCombobox.Contentto search. -
Combobox.CustomTrigger: A headless trigger for building a structurally different trigger element. -
Combobox.Value: Renders the current selection, useful inside aCombobox.CustomTrigger. -
Combobox.MultiTrigger: A multi-select field that renders the selected values as removable labels. -
Combobox.Label: A removable label representing one selected value, rendered byCombobox.MultiTrigger. -
Combobox.Content: The popup rendered when the combobox is open. It positions itself against the field. -
Combobox.List: Renders the filtered items via a render function — return oneCombobox.Itemper item. -
Combobox.VirtualList: A windowed alternative toCombobox.Listfor large lists, rendering only the visible rows. -
Combobox.Item: A selectable option. -
Combobox.ItemIndicator: A visual cue (a check icon) displayed when the item is selected. -
Combobox.NoValueFound: The content displayed when no item matches the query. -
Combobox.Clear: A button that clears the selection.
Examples#
Basic#
Combobox displays a list of items for the user to pick from. By default Combobox.TextInput acts as a typeahead — printable characters open the dropdown and filter the list (a case-insensitive contains match), and the first match is highlighted automatically.
Pass your data to Combobox.Root via the items prop and render each item inside Combobox.List. The combobox filters items against the typed query and hands the matching ones to your render function.
Provide a placeholder on Combobox.TextInput when the combobox has no initial value.
With default value#
An initial, uncontrolled selection can be provided with the defaultValue prop. The matching item is highlighted in the list and its label fills the input.
Controlled value#
Make the Combobox controlled by passing your own state to value and handling onValueChange. Pass null for "no selection".
Controlled input value#
The text typed in the field is exposed separately from the selection. Control it with inputValue and onInputValueChange — useful when you filter the data yourself (set filter={null} and feed already-filtered results to items).
With a clear button#
Render Combobox.Clear to add a button that clears the selection. Focus moves back to Combobox.TextInput after clearing.
Combobox.Clear renders only when there is a value to clear, and never on a required combobox (an emptied required field would be invalid).
Disabled#
Set isDisabled on Combobox.Root to disable the whole field — it can't be focused, typed in, or opened, and its value is excluded from form submission. To disable individual items instead, set isDisabled on Combobox.Item; those items stay visible and filterable but can't be highlighted or selected.
With Form Field#
Use the Form Field component to enhance Combobox with an accessible label, optional helper text, a feedback message, or a required indicator. Wrapping comboboxes with Form Field is recommended for a consistent set of accessible form fields.
When using Combobox without Form Field, be mindful to provide an aria-label on Combobox.TextInput, Combobox.Trigger, or Combobox.MultiTrigger.
Sizes#
Combobox comes in two size variants, medium (default) and small, aligned with the sizes of related components like Text Input. When wrapped in a Form Field, set the size on FormField.Root and on Combobox.Root.
Filtering#
By default the list does a substring (contains) match that is case-, accent-, and punctuation-insensitive, powered by Intl.Collator. String items (and the label of { value, label } objects) filter with no extra setup.
To change the strategy, pass one of Combobox.useFilter()'s matchers — startsWith, contains, or endsWith — to the filter prop. The example below matches only from the start of the word; to match on a different property or several fields at once, see Matching additional fields.
Built-in filtering compares in the user's runtime locale; set the locale prop on Combobox.Root to override it. Combobox.useFilter() doesn't inherit that prop, so pass it { locale } directly. See the Intl.Collator documentation for the available options.
To filter the data yourself (e.g. server-side or with a fuzzy matcher), set filter={null} to disable the built-in filtering and pass your already-filtered results as items.
Object items#
Items aren't limited to strings — you can pass objects as value. For a { value, label } shape, the label is automatically used for display and filtering, and the value for form submission. For any other shape, supply itemToStringLabel so typing still filters (and the input fills) by the right text, and itemToStringValue to set the string submitted in a form.
Item content can also be arbitrary markup, such as a two-line cell with a secondary description.
Keep object items referentially stable — define them outside render or memoise with useMemo (as below). The selected value is matched to an item by reference (Object.is), so new objects on each render won't match and the selection is lost. Alternatively, pass isItemEqualToValue to match by a field (e.g. (a, b) => a.id === b.id). String and number items aren't affected.
Matching additional fields#
{ value, label } items match on their label out of the box. To match on more, filter's matchers accept a function that turns each item into the text to match. Here it returns the label joined with the ISO code, so typing "pt-pt" finds Portuguese (Portugal) even though its label never contains it.
Custom value#
By default the value must come from the list. Set allowsCustomValue so a typed value doesn't have to match one of the items. After typing, if the user presses Enter or clicks out of the combobox without choosing a value from the list, the typed string becomes the value.
allowsCustomValue is only available on single-select string comboboxes.
Pair it with Combobox.NoValueFound to hint that the typed text can be added.
Decorated input and items#
Combobox.TextInput and Combobox.Item can be decorated with supporting icons or elements using their startElement and endElement props.
Selected indicator#
A selected item's label is highlighted in bold. For additional emphasis you may render Combobox.ItemIndicator inside the item to add a visual indicator shown when the item is selected.
With inline label#
Use startElement on Combobox.TextInput to render an inline label alongside the typed or selected value, keeping the field compact while still communicating its purpose.
Select with search in popup#
You can trigger the combobox from a Combobox.Trigger button that opens a popup whose Combobox.TextInput filters the list.
Omit the trigger's children and it auto-renders the selected item's label (resolved via itemToStringLabel, the item's label field, or the raw string value).
Pass a function as the trigger's children to render custom markup — here the flag alongside the label.
Multiple selection#
Set multiple on Combobox.Root and use Combobox.MultiTrigger instead of a single Combobox.TextInput. Selected values render as removable labels (Combobox.Label), and the composed Combobox.TextInput types alongside them to filter.
Multiple with search in popup#
Because Combobox.TextInput is composed separately, it can live inside the popup instead of inline — Combobox.MultiTrigger then reads as a multi-select trigger showing only the selected labels.
To keep the field compact with many selections, cap the visible labels with maxVisibleItems and collapse the rest into a +N label using renderOverflow.
Virtualised#
Combobox.VirtualList uses virtualisation to render only the visible rows of a large list. Set virtualized and items on Combobox.Root, swap Combobox.List for Combobox.VirtualList, and return one Combobox.Item per row.
These patterns all work together. Below is an example of a virtualised multi-select with the search in the popup, like an assignee picker.
Custom trigger#
For a fully custom trigger, use Combobox.CustomTrigger — it's headless, so render any element via as and compose Combobox.Value for the selection.
Within an HTML form#
When used inside an HTML form, Combobox automatically syncs the selected value to a hidden input under the name you provide, so it's available from FormData on submit.
Accessibility features#
-
Exposed to assistive technology as a combobox using the WAI ARIA Combobox design pattern.
-
Provides deep keyboard interactions.
API reference#
Combobox.Root#
Prop | Type | Default |
|---|---|---|
items | Item[] | No default value |
value | Value | Value[] | null | No default value |
defaultValue | Value | Value[] | null | No default value |
onValueChange | function | No default value |
inputValue | string | No default value |
defaultInputValue | string | No default value |
onInputValueChange | function | No default value |
isOpen | boolean | false |
defaultIsOpen | boolean | No default value |
onIsOpenChange | function | No default value |
multiple | boolean | false |
filter | function | No default value |
itemToStringLabel | function | No default value |
itemToStringValue | function | No default value |
isItemEqualToValue | function | No default value |
virtualized | boolean | false |
allowsCustomValue | boolean | false |
autoHighlight | boolean | true |
size | enum | "medium" |
isDisabled | boolean | false |
isRequired | boolean | false |
isReadOnly | boolean | false |
onItemHighlighted | function | No default value |
locale | string | No default value |
name | string | No default value |
Combobox.TextInput#
Prop | Type | Default |
|---|---|---|
css | StitchesCss | No default value |
startElement | React.ReactElement | No default value |
endElement | React.ReactElement | No default value |
shouldShowOpenIndicator | boolean | No default value |
openIndicatorLabel | string | "Show options" |
validationStatus | enum | No default value |
size | enum | No default value |
Combobox.Trigger#
Prop | Type | Default |
|---|---|---|
css | StitchesCss | No default value |
placeholder | React.ReactNode | No default value |
children | enum | No default value |
startElement | React.ReactElement | No default value |
endElement | React.ReactElement | No default value |
size | enum | No default value |
Combobox.CustomTrigger#
Prop | Type | Default |
|---|---|---|
as | enum | button |
nativeButton | boolean | true |
css | StitchesCss | No default value |
Combobox.Value#
Prop | Type | Default |
|---|---|---|
placeholder | React.ReactNode | No default value |
children | enum | No default value |
Combobox.MultiTrigger#
Prop | Type | Default |
|---|---|---|
renderItem* | function | No default value |
maxVisibleItems | number | No default value |
renderOverflow | function | No default value |
placeholder | React.ReactNode | No default value |
children | React.ReactNode | No default value |
startElement | React.ReactElement | No default value |
endElement | React.ReactElement | No default value |
shouldShowOpenIndicator | boolean | true |
openIndicatorLabel | string | "Show options" |
validationStatus | enum | No default value |
size | enum | No default value |
css | StitchesCss | No default value |
Combobox.Label#
Prop | Type | Default |
|---|---|---|
variant | enum | "subtle" |
color | enum | "neutral" |
isDisabled | boolean | No default value |
shouldShowRemove | boolean | true |
removeLabel | string | "Remove" |
startElement | React.ReactElement | No default value |
css | StitchesCss | No default value |
Combobox.Content#
Prop | Type | Default |
|---|---|---|
css | StitchesCss | No default value |
placement | enum | "bottom" |
anchorOffset | number | No default value |
portalContainer | HTMLElement | React.RefObject<HTMLElement> | No default value |
Combobox.List#
Prop | Type | Default |
|---|---|---|
children* | function | No default value |
css | StitchesCss | No default value |
Combobox.VirtualList#
Prop | Type | Default |
|---|---|---|
children* | function | No default value |
estimateSize | number | No default value |
overscan | number | 10 |
maxHeight | StitchesCss['maxHeight'] | "25rem" |
css | StitchesCss | No default value |
Combobox.Item#
Prop | Type | Default |
|---|---|---|
value* | Value | No default value |
as | enum | div |
css | StitchesCss | No default value |
isDisabled | boolean | No default value |
index | number | No default value |
startElement | React.ReactElement | No default value |
endElement | React.ReactElement | No default value |
nativeButton | boolean | No default value |
Combobox.ItemIndicator#
Prop | Type | Default |
|---|---|---|
as | enum | div |
css | StitchesCss | No default value |
Combobox.NoValueFound#
Prop | Type | Default |
|---|---|---|
as | enum | div |
css | StitchesCss | No default value |
Combobox.Clear#
Prop | Type | Default |
|---|---|---|
label* | string | No default value |
css | StitchesCss | No default value |
Combobox.useFilter#
A hook returning collator-based matchers — contains, startsWith, and endsWith — for building a filter predicate. Each takes the item, the query, and an optional itemToString. Pass Intl.Collator options (e.g. { sensitivity: 'accent' }) to override the default case-, accent-, and punctuation-insensitive matching.
const { startsWith } = Combobox.useFilter();<Combobox.Root items={items} filter={startsWith}>{/* … */}</Combobox.Root>;