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 a Combobox.TextInput placed inside Combobox.Content to search.
  • ​
    Combobox.CustomTrigger: A headless trigger for building a structurally different trigger element.
  • ​
    Combobox.Value: Renders the current selection, useful inside a Combobox.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 by Combobox.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 one Combobox.Item per item.
  • ​
    Combobox.VirtualList: A windowed alternative to Combobox.List for 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#


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>;