# Combobox
> Combobox combines a text input with a list of options that users are able to select from.

## Import

```js
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.

> **Tip**
>
> 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.

> **Tip**
>
> Provide a `placeholder` on `Combobox.TextInput` when the combobox has no initial value.

```jsx
<Combobox.Root
  items={[
    'Apple',
    'Avocado',
    'Banana',
    'Blueberry',
    'Cherry',
    'Grape',
    'Kiwi',
    'Mango',
    'Orange',
    'Strawberry'
  ]}
>
  <Combobox.TextInput aria-label="Fruit" placeholder="Pick a fruit…" />
  <Combobox.Content>
    <Combobox.List>
      {item => (
        <Combobox.Item key={item} value={item}>
          {item}
        </Combobox.Item>
      )}
    </Combobox.List>
    <Combobox.NoValueFound>{'No fruit found'}</Combobox.NoValueFound>
  </Combobox.Content>
</Combobox.Root>
```

### 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.

```jsx
<Combobox.Root
  defaultValue="Banana"
  items={['Apple', 'Banana', 'Grape', 'Orange', 'Strawberry', 'Mango']}
>
  <Combobox.TextInput aria-label="Fruit" placeholder="Pick a fruit…" />
  <Combobox.Content>
    <Combobox.List>
      {item => (
        <Combobox.Item key={item} value={item}>
          {item}
        </Combobox.Item>
      )}
    </Combobox.List>
    <Combobox.NoValueFound>{'No fruit found'}</Combobox.NoValueFound>
  </Combobox.Content>
</Combobox.Root>
```

### Controlled value

Make the `Combobox` controlled by passing your own state to `value` and handling `onValueChange`. Pass `null` for "no selection".

```jsx
() => {
  const [value, setValue] = React.useState(null);
  const fruits = ['Apple', 'Avocado', 'Banana', 'Kiwi', 'Mango', 'Orange'];

  return (
    <Flex flow="column">
      <Combobox.Root items={fruits} value={value} onValueChange={setValue}>
        <Combobox.TextInput
          aria-label="Fruit"
          placeholder="Select favourite fruit…"
          endElement={<Combobox.Clear label="Clear" />}
        />
        <Combobox.Content>
          <Combobox.List>
            {item => (
              <Combobox.Item key={item} value={item}>
                {item}
              </Combobox.Item>
            )}
          </Combobox.List>
          <Combobox.NoValueFound>{'No fruit found'}</Combobox.NoValueFound>
        </Combobox.Content>
      </Combobox.Root>
      {value ? <Box>{`Your favourite fruit is: ${value}`}</Box> : null}
    </Flex>
  );
};
```

### 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`).

```jsx
() => {
  const [value, setValue] = React.useState(null);
  const [inputValue, setInputValue] = React.useState('');
  const fruits = ['Apple', 'Avocado', 'Banana', 'Kiwi', 'Mango', 'Orange'];

  return (
    <Flex flow="column">
      <Flex flow="column" gap="spacingXs">
        <Box>{`Selected value: ${value ?? ''}`}</Box>
        <Box>{`Input value: ${inputValue}`}</Box>
      </Flex>
      <Combobox.Root
        items={fruits}
        value={value}
        onValueChange={setValue}
        inputValue={inputValue}
        onInputValueChange={setInputValue}
      >
        <Combobox.TextInput
          aria-label="Fruit"
          placeholder="Select favourite fruit…"
        />
        <Combobox.Content>
          <Combobox.List>
            {item => (
              <Combobox.Item key={item} value={item}>
                {item}
              </Combobox.Item>
            )}
          </Combobox.List>
          <Combobox.NoValueFound>{'No fruit found'}</Combobox.NoValueFound>
        </Combobox.Content>
      </Combobox.Root>
    </Flex>
  );
};
```

### With a clear button

Render `Combobox.Clear` to add a button that clears the selection. Focus moves back to `Combobox.TextInput` after clearing.

> **Tip**
>
> `Combobox.Clear` renders only when there is a value to clear, and never on a required combobox (an emptied required field would be invalid).

```jsx
<Combobox.Root
  defaultValue="Grape"
  items={['Apple', 'Banana', 'Grape', 'Orange']}
>
  <Combobox.TextInput
    aria-label="Fruit"
    placeholder="Select favourite fruit…"
    endElement={<Combobox.Clear label="Clear" />}
  />
  <Combobox.Content>
    <Combobox.List>
      {item => (
        <Combobox.Item key={item} value={item}>
          {item}
        </Combobox.Item>
      )}
    </Combobox.List>
    <Combobox.NoValueFound>{'No fruit found'}</Combobox.NoValueFound>
  </Combobox.Content>
</Combobox.Root>
```

### 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.

```jsx
<Flex flow="column">
  <Combobox.Root
    isDisabled
    defaultValue="English"
    items={['English', 'French']}
  >
    <Combobox.TextInput
      aria-label="Language"
      placeholder="Select language"
      endElement={<Combobox.Clear label="Clear" />}
    />
    <Combobox.Content>
      <Combobox.List>
        {item => (
          <Combobox.Item key={item} value={item}>
            {item}
          </Combobox.Item>
        )}
      </Combobox.List>
    </Combobox.Content>
  </Combobox.Root>
  <Combobox.Root
    items={['English', 'French', 'German', 'Spanish', 'Portuguese', 'Korean']}
  >
    <Combobox.TextInput
      aria-label="Language"
      placeholder="Select language"
      endElement={<Combobox.Clear label="Clear" />}
    />
    <Combobox.Content>
      <Combobox.List>
        {item => (
          <Combobox.Item
            key={item}
            value={item}
            isDisabled={item === 'English' || item === 'French'}
          >
            {item}
          </Combobox.Item>
        )}
      </Combobox.List>
      <Combobox.NoValueFound>{'No language found'}</Combobox.NoValueFound>
    </Combobox.Content>
  </Combobox.Root>
</Flex>
```

### With Form Field

Use the [Form Field component](https://next.wave-design-system.pages.dev/components/forms/form-field.md) to enhance `Combobox` with an accessible label, optional helper text, a feedback message, or a required indicator. Wrapping comboboxes with [Form Field](https://next.wave-design-system.pages.dev/components/forms/form-field.md) is recommended for a consistent set of accessible form fields.

> **Important**
>
> When using `Combobox` without [Form Field](https://next.wave-design-system.pages.dev/components/forms/form-field.md), be mindful to provide an `aria-label` on `Combobox.TextInput`, `Combobox.Trigger`, or `Combobox.MultiTrigger`.

```jsx
() => {
  const [value, setValue] = React.useState(null);
  const food = [
    'Apple',
    'Banana',
    'Burger',
    'Cake',
    'Carrot',
    'Cherry',
    'Cookie',
    'Donut',
    'Fries',
    'Grape',
    'Pasta',
    'Pizza',
    'Salad',
    'Sandwich',
    'Strawberry',
    'Tomato'
  ];

  return (
    <FormField.Root
      as={Flex}
      flow="column"
      isRequired
      validationStatus={!value ? 'error' : undefined}
    >
      <FormField.Label>{'Food'}</FormField.Label>
      <Combobox.Root items={food} value={value} onValueChange={setValue}>
        <Combobox.TextInput placeholder="My favourite food is…" />
        <Combobox.Content>
          <Combobox.List>
            {item => (
              <Combobox.Item key={item} value={item}>
                {item}
              </Combobox.Item>
            )}
          </Combobox.List>
          <Combobox.NoValueFound>{'No food found'}</Combobox.NoValueFound>
        </Combobox.Content>
      </Combobox.Root>
      <FormField.FeedbackMessage>
        {'Please pick a food.'}
      </FormField.FeedbackMessage>
    </FormField.Root>
  );
};
```

### Sizes

`Combobox` comes in two size variants, `medium` (default) and `small`, aligned with the sizes of related components like [Text Input](https://next.wave-design-system.pages.dev/components/forms/text-input.md). When wrapped in a [Form Field](https://next.wave-design-system.pages.dev/components/forms/form-field.md), set the size on `FormField.Root` and on `Combobox.Root`.

```jsx
() => {
  const animals = ['Bear', 'Dog', 'Elephant', 'Parrot', 'Raccoon', 'Seal'];

  return (
    <ContentBlock max="measureMedium" center={false} gutters="none">
      <Flex flow="column">
        <FormField.Root as={Flex} flow="column" size="small">
          <FormField.Label>{'Animal'}</FormField.Label>
          <Combobox.Root size="small" items={animals}>
            <Combobox.TextInput endElement={<Combobox.Clear label="Clear" />} />
            <Combobox.Content>
              <Combobox.List>
                {item => (
                  <Combobox.Item key={item} value={item}>
                    {item}
                  </Combobox.Item>
                )}
              </Combobox.List>
              <Combobox.NoValueFound>{'No animal found'}</Combobox.NoValueFound>
            </Combobox.Content>
          </Combobox.Root>
        </FormField.Root>
        <FormField.Root as={Flex} flow="column">
          <FormField.Label>{'Animal'}</FormField.Label>
          <Combobox.Root items={animals}>
            <Combobox.TextInput endElement={<Combobox.Clear label="Clear" />} />
            <Combobox.Content>
              <Combobox.List>
                {item => (
                  <Combobox.Item key={item} value={item}>
                    {item}
                  </Combobox.Item>
                )}
              </Combobox.List>
              <Combobox.NoValueFound>{'No animal found'}</Combobox.NoValueFound>
            </Combobox.Content>
          </Combobox.Root>
        </FormField.Root>
      </Flex>
    </ContentBlock>
  );
};
```

### 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](#matching-additional-fields).

> **Tip**
>
> 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 <Link isExternal href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Collator">Intl.Collator documentation</Link> for the available options.

> **Tip**
>
> 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`.

```jsx
() => {
  const { startsWith } = Combobox.useFilter();
  const fruits = [
    'Apple',
    'Apricot',
    'Avocado',
    'Banana',
    'Blackberry',
    'Blueberry',
    'Cherry',
    'Grape',
    'Mango',
    'Orange'
  ];

  return (
    <Combobox.Root items={fruits} filter={startsWith}>
      <Combobox.TextInput
        aria-label="Fruit"
        placeholder="Type to match the start…"
      />
      <Combobox.Content>
        <Combobox.List>
          {item => (
            <Combobox.Item key={item} value={item}>
              {item}
            </Combobox.Item>
          )}
        </Combobox.List>
        <Combobox.NoValueFound>{'No fruit found'}</Combobox.NoValueFound>
      </Combobox.Content>
    </Combobox.Root>
  );
};
```

### 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.

> **Important**
>
> 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.

```jsx
() => {
  const team = React.useMemo(
    () => [
      { id: '1', name: 'Avery Johnson', department: 'Design' },
      { id: '2', name: 'Bianca Rossi', department: 'Engineering' },
      { id: '3', name: 'Caleb Nguyen', department: 'Engineering' },
      { id: '4', name: 'Dahlia Okonkwo', department: 'Product' },
      { id: '5', name: 'Elias Berg', department: 'Analytics' },
      { id: '6', name: 'Farah Haddad', department: 'Design' },
      { id: '7', name: 'Gabriel Santos', department: 'Engineering' },
      { id: '8', name: 'Hana Kim', department: 'Marketing' }
    ],
    []
  );

  return (
    <Combobox.Root items={team} itemToStringLabel={member => member.name}>
      <Combobox.TextInput aria-label="Teammate" placeholder="Search people…" />
      <Combobox.Content>
        <Combobox.List>
          {member => (
            <Combobox.Item
              as={Flex}
              key={member.id}
              value={member}
              flow="column"
              gap="none"
            >
              <Text>{member.name}</Text>
              <Text variant="eta" color="foregroundNeutralSubtle">
                {member.department}
              </Text>
            </Combobox.Item>
          )}
        </Combobox.List>
        <Combobox.NoValueFound>{'No people found'}</Combobox.NoValueFound>
      </Combobox.Content>
    </Combobox.Root>
  );
};
```

### 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.

```jsx
() => {
  const filter = Combobox.useFilter();
  const languages = React.useMemo(
    () => [
      { value: 'en', label: 'English', flag: '🇬🇧' },
      { value: 'cs', label: 'Čeština', flag: '🇨🇿' },
      { value: 'de', label: 'Deutsch', flag: '🇩🇪' },
      { value: 'es', label: 'Español', flag: '🇪🇸' },
      { value: 'fr', label: 'Français', flag: '🇫🇷' },
      { value: 'pt-br', label: 'Português (Brasil)', flag: '🇧🇷' },
      { value: 'pt-pt', label: 'Português (Portugal)', flag: '🇵🇹' },
      { value: 'sv', label: 'Svenska', flag: '🇸🇪' }
    ],
    []
  );

  const matchNameOrCode = React.useCallback(
    (item, query) =>
      filter.contains(
        item,
        query,
        language => `${language.label} ${language.value}`
      ),
    [filter]
  );

  return (
    <Combobox.Root
      items={languages}
      defaultValue={languages[0]}
      filter={matchNameOrCode}
    >
      <Combobox.Trigger aria-label="Language" placeholder="Select language">
        {language =>
          language ? (
            <Flex gap="spacingXs">
              <span>{language.flag}</span>
              <Text overflowStrategy="truncate">{language.label}</Text>
            </Flex>
          ) : null
        }
      </Combobox.Trigger>
      <Combobox.Content>
        <Combobox.TextInput
          aria-label="Search languages"
          placeholder="Search by name or code (e.g. pt-pt)…"
        />
        <Combobox.List>
          {item => (
            <Combobox.Item
              key={item.value}
              value={item}
              startElement={<span>{item.flag}</span>}
              endElement={<Combobox.ItemIndicator />}
            >
              {item.label}
            </Combobox.Item>
          )}
        </Combobox.List>
        <Combobox.NoValueFound>{'No languages found'}</Combobox.NoValueFound>
      </Combobox.Content>
    </Combobox.Root>
  );
};
```

### 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.

> **Important**
>
> `allowsCustomValue` is only available on single-select string comboboxes.

> **Tip**
>
> Pair it with `Combobox.NoValueFound` to hint that the typed text can be added.

```jsx
() => {
  const [value, setValue] = React.useState(null);
  const [inputValue, setInputValue] = React.useState('');
  const fruits = ['Apple', 'Avocado', 'Banana', 'Kiwi', 'Mango', 'Orange'];

  return (
    <Flex flow="column">
      {value ? <Box>{`Selected value: ${value}`}</Box> : null}
      <Combobox.Root
        items={fruits}
        allowsCustomValue
        value={value}
        onValueChange={setValue}
        inputValue={inputValue}
        onInputValueChange={setInputValue}
      >
        <Combobox.TextInput
          aria-label="Fruit"
          placeholder="Pick a fruit or type your own…"
          endElement={<Combobox.Clear label="Clear" />}
        />
        <Combobox.Content>
          <Combobox.List>
            {item => (
              <Combobox.Item key={item} value={item}>
                {item}
              </Combobox.Item>
            )}
          </Combobox.List>
          <Combobox.NoValueFound>
            {'No match for “'}
            <b>{inputValue}</b>
            {'” — press '}
            <Kbd>{'Enter'}</Kbd>
            {' to add it.'}
          </Combobox.NoValueFound>
        </Combobox.Content>
      </Combobox.Root>
    </Flex>
  );
};
```

### Decorated input and items

`Combobox.TextInput` and `Combobox.Item` can be decorated with supporting icons or elements using their `startElement` and `endElement` props.

```jsx
() => {
  const statuses = React.useMemo(
    () => [
      { value: 'backlog', label: 'Backlog', icon: 'help', count: 8 },
      { value: 'todo', label: 'Todo', icon: 'tasks', count: 8 },
      { value: 'in-progress', label: 'In Progress', icon: 'loading', count: 3 },
      { value: 'done', label: 'Done', icon: 'check', count: 4 },
      { value: 'cancelled', label: 'Cancelled', icon: 'close', count: 10 }
    ],
    []
  );

  return (
    <ContentBlock max="measureNarrow" center={false} gutters="none">
      <Combobox.Root
        items={statuses}
        itemToStringLabel={status => status.label}
      >
        <Combobox.TextInput
          aria-label="Status"
          startElement={<SvgIcon iconName="search" />}
          endElement={<Combobox.Clear label="Clear" />}
          placeholder="Change status…"
        />
        <Combobox.Content>
          <Combobox.List>
            {status => (
              <Combobox.Item
                key={status.value}
                value={status}
                startElement={<SvgIcon iconName={status.icon} />}
                endElement={
                  <Label size="small" pill>
                    {status.count}
                  </Label>
                }
              >
                {status.label}
              </Combobox.Item>
            )}
          </Combobox.List>
          <Combobox.NoValueFound>{'No status found'}</Combobox.NoValueFound>
        </Combobox.Content>
      </Combobox.Root>
    </ContentBlock>
  );
};
```

### 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.

```jsx
() => {
  const languages = React.useMemo(
    () => [
      { value: 'en', label: 'English' },
      { value: 'fr', label: 'French' },
      { value: 'de', label: 'German' },
      { value: 'es', label: 'Spanish' },
      { value: 'pt', label: 'Portuguese' },
      { value: 'ru', label: 'Russian' }
    ],
    []
  );

  return (
    <ContentBlock max="measureNarrow" center={false} gutters="none">
      <Combobox.Root items={languages} itemToStringLabel={lang => lang.label}>
        <Combobox.TextInput
          aria-label="Language"
          placeholder="Select language"
          endElement={<Combobox.Clear label="Clear" />}
        />
        <Combobox.Content>
          <Combobox.List>
            {lang => (
              <Combobox.Item
                key={lang.value}
                value={lang}
                startElement={<Combobox.ItemIndicator />}
              >
                {lang.label}
              </Combobox.Item>
            )}
          </Combobox.List>
          <Combobox.NoValueFound>{'No language found'}</Combobox.NoValueFound>
        </Combobox.Content>
      </Combobox.Root>
    </ContentBlock>
  );
};
```

### 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.

```jsx
<Combobox.Root
  items={[
    'Apple',
    'Avocado',
    'Banana',
    'Kiwi',
    'Mango',
    'Orange',
    'Strawberry'
  ]}
>
  <Combobox.TextInput
    aria-label="Fruit"
    startElement={
      <Text color="foregroundNeutralSubtle">{'My favourite fruit is: '}</Text>
    }
    endElement={<Combobox.Clear label="Clear" />}
  />
  <Combobox.Content>
    <Combobox.List>
      {item => (
        <Combobox.Item key={item} value={item}>
          {item}
        </Combobox.Item>
      )}
    </Combobox.List>
    <Combobox.NoValueFound>{'No fruit found'}</Combobox.NoValueFound>
  </Combobox.Content>
</Combobox.Root>
```

### 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).

```jsx
() => {
  const languages = React.useMemo(
    () => [
      { value: 'en', label: 'English', flag: '🇬🇧' },
      { value: 'de', label: 'Deutsch', flag: '🇩🇪' },
      { value: 'es', label: 'Español', flag: '🇪🇸' },
      { value: 'fr', label: 'Français', flag: '🇫🇷' },
      { value: 'sv', label: 'Svenska', flag: '🇸🇪' }
    ],
    []
  );

  return (
    <Combobox.Root items={languages} defaultValue={languages[0]}>
      <Combobox.Trigger aria-label="Language" placeholder="Select language" />
      <Combobox.Content>
        <Combobox.TextInput
          aria-label="Search languages"
          placeholder="Search languages"
        />
        <Combobox.List>
          {item => (
            <Combobox.Item
              key={item.value}
              value={item}
              startElement={<span>{item.flag}</span>}
              endElement={<Combobox.ItemIndicator />}
            >
              {item.label}
            </Combobox.Item>
          )}
        </Combobox.List>
        <Combobox.NoValueFound>{'No languages found'}</Combobox.NoValueFound>
      </Combobox.Content>
    </Combobox.Root>
  );
};
```

Pass a function as the trigger's children to render custom markup — here the flag alongside the label.

```jsx
() => {
  const languages = React.useMemo(
    () => [
      { value: 'en', label: 'English', flag: '🇬🇧' },
      { value: 'cs', label: 'Čeština', flag: '🇨🇿' },
      { value: 'de', label: 'Deutsch', flag: '🇩🇪' },
      { value: 'es', label: 'Español', flag: '🇪🇸' },
      { value: 'fr', label: 'Français', flag: '🇫🇷' },
      { value: 'it', label: 'Italiano', flag: '🇮🇹' },
      { value: 'sv', label: 'Svenska', flag: '🇸🇪' },
      { value: 'vi', label: 'Tiếng Việt', flag: '🇻🇳' }
    ],
    []
  );

  return (
    <Combobox.Root items={languages} defaultValue={languages[0]}>
      <Combobox.Trigger aria-label="Language" placeholder="Select language">
        {language =>
          language ? (
            <Flex gap="spacingXs">
              <span>{language.flag}</span>
              <Text overflowStrategy="truncate">{language.label}</Text>
            </Flex>
          ) : null
        }
      </Combobox.Trigger>
      <Combobox.Content>
        <Combobox.TextInput
          aria-label="Search languages"
          placeholder="Search languages"
        />
        <Combobox.List>
          {item => (
            <Combobox.Item
              key={item.value}
              value={item}
              startElement={<span>{item.flag}</span>}
              endElement={<Combobox.ItemIndicator />}
            >
              {item.label}
            </Combobox.Item>
          )}
        </Combobox.List>
        <Combobox.NoValueFound>{'No languages found'}</Combobox.NoValueFound>
      </Combobox.Content>
    </Combobox.Root>
  );
};
```

### 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.

```jsx
() => {
  const [value, setValue] = React.useState(['documentation', 'feature']);
  const labels = [
    'bug',
    'documentation',
    'duplicate',
    'enhancement',
    'feature',
    'good first issue',
    'help wanted',
    'invalid',
    'question',
    'wontfix'
  ];

  return (
    <Combobox.Root
      multiple
      items={labels}
      value={value}
      onValueChange={setValue}
    >
      <Combobox.MultiTrigger
        renderItem={item => <Combobox.Label key={item}>{item}</Combobox.Label>}
        endElement={<Combobox.Clear label="Clear all" />}
      >
        <Combobox.TextInput aria-label="Labels" placeholder="Add labels…" />
      </Combobox.MultiTrigger>
      <Combobox.Content>
        <Combobox.List>
          {item => (
            <Combobox.Item
              key={item}
              value={item}
              startElement={<Combobox.ItemIndicator />}
            >
              {item}
            </Combobox.Item>
          )}
        </Combobox.List>
        <Combobox.NoValueFound>{'No labels found'}</Combobox.NoValueFound>
      </Combobox.Content>
    </Combobox.Root>
  );
};
```

### 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`.

```jsx
() => {
  const [value, setValue] = React.useState([
    'bug',
    'documentation',
    'enhancement',
    'help wanted',
    'good first issue'
  ]);
  const labels = [
    'bug',
    'documentation',
    'duplicate',
    'enhancement',
    'feature',
    'good first issue',
    'help wanted',
    'invalid',
    'question',
    'wontfix'
  ];

  return (
    <Combobox.Root
      multiple
      items={labels}
      value={value}
      onValueChange={setValue}
    >
      <Combobox.MultiTrigger
        aria-label="Labels"
        placeholder="Select labels…"
        maxVisibleItems={4}
        renderItem={item => <Combobox.Label key={item}>{item}</Combobox.Label>}
        renderOverflow={hiddenCount => (
          <Label variant="subtle">{`+${hiddenCount}`}</Label>
        )}
        endElement={<Combobox.Clear label="Clear all" />}
      />
      <Combobox.Content>
        <Combobox.TextInput
          aria-label="Search labels"
          placeholder="Search labels…"
          startElement={<SvgIcon iconName="search" />}
        />
        <Combobox.List>
          {item => (
            <Combobox.Item
              key={item}
              value={item}
              startElement={<Combobox.ItemIndicator />}
            >
              {item}
            </Combobox.Item>
          )}
        </Combobox.List>
        <Combobox.NoValueFound>{'No labels found'}</Combobox.NoValueFound>
      </Combobox.Content>
    </Combobox.Root>
  );
};
```

### Virtualised

`Combobox.VirtualList` uses <Link isExternal href="https://tanstack.com/virtual/v3">virtualisation</Link> 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.

```jsx
() => {
  const items = React.useMemo(
    () =>
      Array.from({ length: 10000 }, (_, index) => {
        const id = String(index + 1);
        return { id, name: `Item ${id.padStart(5, '0')}` };
      }),
    []
  );

  return (
    <Combobox.Root
      virtualized
      items={items}
      itemToStringLabel={item => item.name}
    >
      <Combobox.TextInput
        aria-label="Item"
        placeholder="Search 10,000 items…"
        startElement={<SvgIcon iconName="search" />}
        endElement={<Combobox.Clear label="Clear" />}
      />
      <Combobox.Content>
        <Combobox.VirtualList>
          {item => <Combobox.Item value={item}>{item.name}</Combobox.Item>}
        </Combobox.VirtualList>
        <Combobox.NoValueFound>{'No items found'}</Combobox.NoValueFound>
      </Combobox.Content>
    </Combobox.Root>
  );
};
```

These patterns all work together. Below is an example of a virtualised multi-select with the search in the popup, like an assignee picker.

```jsx
() => {
  const people = React.useMemo(
    () =>
      Array.from({ length: 1000 }, (_, index) => ({
        id: String(index),
        name: `${faker.person.firstName()} ${faker.person.lastName()}`
      })),
    []
  );
  const [value, setValue] = React.useState(() => people.slice(0, 8));

  return (
    <Combobox.Root
      multiple
      virtualized
      items={people}
      itemToStringLabel={person => person.name}
      value={value}
      onValueChange={setValue}
    >
      <Combobox.MultiTrigger
        aria-label="Assignees"
        placeholder="Assign teammates…"
        maxVisibleItems={6}
        renderItem={person => (
          <Combobox.Label key={person.id}>{person.name}</Combobox.Label>
        )}
        renderOverflow={hiddenCount => (
          <Label variant="subtle">{`+${hiddenCount}`}</Label>
        )}
        endElement={<Combobox.Clear label="Clear all" />}
      />
      <Combobox.Content>
        <Combobox.TextInput
          aria-label="Search people"
          placeholder="Search people…"
          startElement={<SvgIcon iconName="search" />}
        />
        <Combobox.VirtualList estimateSize={44}>
          {person => (
            <Combobox.Item
              value={person}
              startElement={
                <Avatar.Root of={person.name} size="small">
                  <Avatar.Fallback>
                    <Avatar.Initials color="auto" />
                  </Avatar.Fallback>
                </Avatar.Root>
              }
              endElement={<Combobox.ItemIndicator />}
            >
              {person.name}
            </Combobox.Item>
          )}
        </Combobox.VirtualList>
        <Combobox.NoValueFound>{'No people found'}</Combobox.NoValueFound>
      </Combobox.Content>
    </Combobox.Root>
  );
};
```

### 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.

```jsx
() => {
  const languages = React.useMemo(
    () => [
      { value: 'en', label: 'English', flag: '🇬🇧' },
      { value: 'de', label: 'Deutsch', flag: '🇩🇪' },
      { value: 'es', label: 'Español', flag: '🇪🇸' },
      { value: 'fr', label: 'Français', flag: '🇫🇷' },
      { value: 'sv', label: 'Svenska', flag: '🇸🇪' }
    ],
    []
  );

  return (
    <Combobox.Root items={languages} defaultValue={languages[0]}>
      <Combobox.CustomTrigger
        as={Button}
        rightIcon={<SvgIcon iconName="chevronDown" />}
      >
        <Combobox.Value />
      </Combobox.CustomTrigger>
      <Combobox.Content placement="bottom_left" css={{ minWidth: '20rem' }}>
        <Combobox.TextInput
          aria-label="Search languages"
          placeholder="Search languages"
        />
        <Combobox.List>
          {item => (
            <Combobox.Item
              key={item.value}
              value={item}
              startElement={<span>{item.flag}</span>}
              endElement={<Combobox.ItemIndicator />}
            >
              {item.label}
            </Combobox.Item>
          )}
        </Combobox.List>
        <Combobox.NoValueFound>{'No languages found'}</Combobox.NoValueFound>
      </Combobox.Content>
    </Combobox.Root>
  );
};
```

### 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.

```jsx
() => {
  const [data, setData] = React.useState({});
  const cities = ['Rome', 'Oslo', 'Paris', 'New York'];

  return (
    <form
      onSubmit={event => {
        event.preventDefault();
        const formData = new FormData(event.currentTarget);
        setData(Object.fromEntries(formData.entries()));
      }}
    >
      <Flex flow="column">
        <FormField.Root as={Flex} flow="column">
          <FormField.Label>{'City'}</FormField.Label>
          <Combobox.Root name="city" items={cities}>
            <Combobox.TextInput endElement={<Combobox.Clear label="Clear" />} />
            <Combobox.Content>
              <Combobox.List>
                {item => (
                  <Combobox.Item key={item} value={item}>
                    {item}
                  </Combobox.Item>
                )}
              </Combobox.List>
              <Combobox.NoValueFound>{'No city found'}</Combobox.NoValueFound>
            </Combobox.Content>
          </Combobox.Root>
        </FormField.Root>
        <Box>
          <Button type="submit">{'Submit'}</Button>
        </Box>
        <Box>
          <pre>
            <code>{JSON.stringify(data, null, 2)}</code>
          </pre>
        </Box>
      </Flex>
    </form>
  );
};
```

---

## Accessibility features

<ul>
  <li iconName="check">

Exposed to assistive technology as a combobox using the [WAI ARIA Combobox design pattern](https://www.w3.org/WAI/ARIA/apg/patterns/combobox/).

  </li>
  <li iconName="check">

Provides deep keyboard interactions.

  </li>
</ul>

---

## API reference

### Combobox.Root

| Name                 | Type                                              | Default    | Description                                                                                                                                                                                                                                  | Required |
| -------------------- | ------------------------------------------------- | ---------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- |
| `items`              | `Item[]`                                          |            | The data the list renders and filters. Each `Combobox.List` / `Combobox.VirtualList` render function receives one matching item. Items can be strings or objects.                                                                            |          |
| `value`              | `Value \| Value[] \| null`                        |            | The controlled selected value (an array when `multiple`). Use in conjunction with `onValueChange`.                                                                                                                                           |          |
| `defaultValue`       | `Value \| Value[] \| null`                        |            | The selected value when initially rendered. Use when you do not need to control the selection state.                                                                                                                                         |          |
| `onValueChange`      | `(value, eventDetails) => void`                   |            | Event handler called when the selection changes.                                                                                                                                                                                             |          |
| `inputValue`         | `string`                                          |            | The controlled value of the text input. Use in conjunction with `onInputValueChange`.                                                                                                                                                        |          |
| `defaultInputValue`  | `string`                                          |            | The value of the text input when first rendered. Use when you do not need to control the input value.                                                                                                                                        |          |
| `onInputValueChange` | `(inputValue, eventDetails) => void`              |            | Event handler called when the text input value changes. The `eventDetails.reason` distinguishes genuine typing (`"input-change"`) from the input syncs emitted on selection or close.                                                        |          |
| `isOpen`             | `boolean`                                         | `false`    | The controlled open state of the popup. Use in conjunction with `onIsOpenChange`.                                                                                                                                                            |          |
| `defaultIsOpen`      | `boolean`                                         |            | The open state of the popup when it's first rendered. Use when you do not need to control the open state.                                                                                                                                    |          |
| `onIsOpenChange`     | `(isOpen, eventDetails) => void`                  |            | Event handler called when the open state of the popup changes.                                                                                                                                                                               |          |
| `multiple`           | `boolean`                                         | `false`    | When `true`, multiple items can be selected and `value` becomes an array. Use with `Combobox.MultiTrigger`.                                                                                                                                  |          |
| `filter`             | `null \| (item, query, itemToString?) => boolean` |            | Predicate that decides whether an item matches the query. Defaults to a case-insensitive `contains` match. Build a custom one with `Combobox.useFilter()`, or pass `null` to disable built-in filtering and supply already-filtered `items`. |          |
| `itemToStringLabel`  | `(item) => string`                                |            | For object items, returns the string shown in the input and used for filtering. Not needed for `{ value, label }` shapes (the `label` is used automatically).                                                                                |          |
| `itemToStringValue`  | `(item) => string`                                |            | For object items, returns the string used for form submission. Not needed for `{ value, label }` shapes (the `value` is used automatically).                                                                                                 |          |
| `isItemEqualToValue` | `(item, value) => boolean`                        |            | Custom equality used to match an item against the selected value. Defaults to `Object.is`; provide it when object items aren't referentially stable.                                                                                         |          |
| `virtualized`        | `boolean`                                         | `false`    | Enables external virtualisation. Required when rendering with `Combobox.VirtualList`.                                                                                                                                                        |          |
| `allowsCustomValue`  | `boolean`                                         | `false`    | When `true`, a non-matching typed value can be committed as the value. Only available on single-select string comboboxes.                                                                                                                    |          |
| `autoHighlight`      | `boolean`                                         | `true`     | Whether the first matching item is highlighted automatically while filtering.                                                                                                                                                                |          |
| `size`               | `"small" \| "medium"`                             | `"medium"` | The size of the combobox.                                                                                                                                                                                                                    |          |
| `isDisabled`         | `boolean`                                         | `false`    | When `true`, the combobox is disabled, preventing the user from interacting with it.                                                                                                                                                         |          |
| `isRequired`         | `boolean`                                         | `false`    | When `true`, the user must choose a value before submitting the owning form.                                                                                                                                                                 |          |
| `isReadOnly`         | `boolean`                                         | `false`    | When `true`, the value can't be changed but the field stays focusable and the list can still be opened and browsed.                                                                                                                          |          |
| `onItemHighlighted`  | `(highlightedValue, eventDetails) => void`        |            | Callback fired when an item is highlighted or unhighlighted.                                                                                                                                                                                 |          |
| `locale`             | `string`                                          |            | Locale used for language-sensitive string comparison during built-in filtering. Defaults to the runtime locale.                                                                                                                              |          |
| `name`               | `string`                                          |            | Name of the hidden input element, used when submitting an HTML form.                                                                                                                                                                         |          |

### Combobox.TextInput

| Name                      | Type                                | Default          | Description                                                                                                                                                                                                                                   | Required |
| ------------------------- | ----------------------------------- | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- |
| `css`                     | `StitchesCss`                       |                  | Apply styles directly to a component in a similar way how you would define inline styles. Wave uses [Stitches](https://stitches.dev/) under the hood with a fully-typed API and support for features like tokens, media queries, or variants. |          |
| `startElement`            | `React.ReactElement`                |                  | `ReactElement` to render to the left of the text input.                                                                                                                                                                                       |          |
| `endElement`              | `React.ReactElement`                |                  | `ReactElement` to render to the right of the text input.                                                                                                                                                                                      |          |
| `shouldShowOpenIndicator` | `boolean`                           |                  | Whether to show the trailing chevron toggle. Defaults to `true` for a standalone field and `false` when rendered inside the popup as a search field.                                                                                          |          |
| `openIndicatorLabel`      | `string`                            | `"Show options"` | Accessible name for the open-indicator toggle button.                                                                                                                                                                                         |          |
| `validationStatus`        | `"error" \| "warning" \| "success"` |                  | Validation styling for the field. Usually inherited from a surrounding [Form Field](https://next.wave-design-system.pages.dev/components/forms/form-field.md).                                                                                |          |
| `size`                    | `"small" \| "medium"`               |                  | Overrides the size inherited from `Combobox.Root`.                                                                                                                                                                                            |          |

### Combobox.Trigger

| Name           | Type                                              | Default | Description                                                                                                                                                                                                                                   | Required |
| -------------- | ------------------------------------------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- |
| `css`          | `StitchesCss`                                     |         | Apply styles directly to a component in a similar way how you would define inline styles. Wave uses [Stitches](https://stitches.dev/) under the hood with a fully-typed API and support for features like tokens, media queries, or variants. |          |
| `placeholder`  | `React.ReactNode`                                 |         | Shown when nothing is selected.                                                                                                                                                                                                               |          |
| `children`     | `React.ReactNode \| ((value) => React.ReactNode)` |         | Renders the selected value. A function receives the current value; any other node renders as-is. Omit to show the auto-resolved label.                                                                                                        |          |
| `startElement` | `React.ReactElement`                              |         | `ReactElement` to render to the left of the value.                                                                                                                                                                                            |          |
| `endElement`   | `React.ReactElement`                              |         | `ReactElement` to render to the right of the value.                                                                                                                                                                                           |          |
| `size`         | `"small" \| "medium"`                             |         | Overrides the size inherited from `Combobox.Root`.                                                                                                                                                                                            |          |

### Combobox.CustomTrigger

| Name           | Type                                                      | Default  | Description                                                                                                                                                                                                                                                                                                                              | Required |
| -------------- | --------------------------------------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- |
| `as`           | `keyof JSX.IntrinsicElements \| React.ComponentType<any>` | `button` | Change the component to a different HTML tag or custom component. This will merge the original component props with the props of the supplied element/component and change the underlying DOM node.   For more details, read our [Composition](https://next.wave-design-system.pages.dev/get-started/composition.md#polymorphism) guide. |          |
| `nativeButton` | `boolean`                                                 | `true`   | Whether the rendered element is a native `<button>`. Set it to `false` when `as` renders a non-button element, so the right ARIA and keyboard behaviour is applied.                                                                                                                                                                      |          |
| `css`          | `StitchesCss`                                             |          | Apply styles directly to a component in a similar way how you would define inline styles. Wave uses [Stitches](https://stitches.dev/) under the hood with a fully-typed API and support for features like tokens, media queries, or variants.                                                                                            |          |

### Combobox.Value

| Name          | Type                                              | Default | Description                                                                                                             | Required |
| ------------- | ------------------------------------------------- | ------- | ----------------------------------------------------------------------------------------------------------------------- | -------- |
| `placeholder` | `React.ReactNode`                                 |         | Shown when nothing is selected, unless overridden by children.                                                          |          |
| `children`    | `React.ReactNode \| ((value) => React.ReactNode)` |         | A function receives the current value to render it; any other node renders as-is. Omit to show the auto-resolved label. |          |

### Combobox.MultiTrigger

| Name                      | Type                                            | Default          | Description                                                                                                                                                                                                                                   | Required |
| ------------------------- | ----------------------------------------------- | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- |
| `renderItem`              | `(item, index) => React.ReactElement`           |                  | Renders one label per selected value — typically a `Combobox.Label`.                                                                                                                                                                          | Yes      |
| `maxVisibleItems`         | `number`                                        |                  | Caps how many selected values render as labels. The remainder is summarised by `renderOverflow`.                                                                                                                                              |          |
| `renderOverflow`          | `(hiddenCount, hiddenItems) => React.ReactNode` |                  | Renders an indicator (typically a `+N` label) for the values hidden by `maxVisibleItems`.                                                                                                                                                     |          |
| `placeholder`             | `React.ReactNode`                               |                  | Shown when nothing is selected. Use this for the search-in-popup pattern; with an inline `Combobox.TextInput`, set its `placeholder` instead.                                                                                                 |          |
| `children`                | `React.ReactNode`                               |                  | Composed field controls — typically a `Combobox.TextInput` for inline filtering. Omit to render only the labels and the open indicator.                                                                                                       |          |
| `startElement`            | `React.ReactElement`                            |                  | `ReactElement` to render before the labels.                                                                                                                                                                                                   |          |
| `endElement`              | `React.ReactElement`                            |                  | `ReactElement` to render to the right of the field.                                                                                                                                                                                           |          |
| `shouldShowOpenIndicator` | `boolean`                                       | `true`           | Whether to show the trailing chevron toggle.                                                                                                                                                                                                  |          |
| `openIndicatorLabel`      | `string`                                        | `"Show options"` | Accessible name for the open-indicator toggle button.                                                                                                                                                                                         |          |
| `validationStatus`        | `"error" \| "warning" \| "success"`             |                  | Validation styling for the field. Usually inherited from a surrounding [Form Field](https://next.wave-design-system.pages.dev/components/forms/form-field.md).                                                                                |          |
| `size`                    | `"small" \| "medium"`                           |                  | Overrides the size inherited from `Combobox.Root`.                                                                                                                                                                                            |          |
| `css`                     | `StitchesCss`                                   |                  | Apply styles directly to a component in a similar way how you would define inline styles. Wave uses [Stitches](https://stitches.dev/) under the hood with a fully-typed API and support for features like tokens, media queries, or variants. |          |

### Combobox.Label

| Name               | Type                                                                    | Default     | Description                                                                                                                                                                                                                                   | Required |
| ------------------ | ----------------------------------------------------------------------- | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- |
| `variant`          | `"strong" \| "subtle" \| "outline"`                                     | `"subtle"`  | The visual variant of the underlying [Label](https://next.wave-design-system.pages.dev/components/label.md).                                                                                                                                  |          |
| `color`            | `"neutral" \| "accent" \| "success" \| "danger" \| "info" \| "warning"` | `"neutral"` | The colour of the underlying Label.                                                                                                                                                                                                           |          |
| `isDisabled`       | `boolean`                                                               |             | When `true`, the label is dimmed and its remove button is suppressed.                                                                                                                                                                         |          |
| `shouldShowRemove` | `boolean`                                                               | `true`      | Whether to render the label remove button.                                                                                                                                                                                                    |          |
| `removeLabel`      | `string`                                                                | `"Remove"`  | Accessible name for the label's remove button.                                                                                                                                                                                                |          |
| `startElement`     | `React.ReactElement`                                                    |             | `ReactElement` to render to the left of the label content.                                                                                                                                                                                    |          |
| `css`              | `StitchesCss`                                                           |             | Apply styles directly to a component in a similar way how you would define inline styles. Wave uses [Stitches](https://stitches.dev/) under the hood with a fully-typed API and support for features like tokens, media queries, or variants. |          |

### Combobox.Content

| Name              | Type                                                                                                     | Default    | Description                                                                                                                                                                                                                                   | Required |
| ----------------- | -------------------------------------------------------------------------------------------------------- | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- |
| `css`             | `StitchesCss`                                                                                            |            | Apply styles directly to a component in a similar way how you would define inline styles. Wave uses [Stitches](https://stitches.dev/) under the hood with a fully-typed API and support for features like tokens, media queries, or variants. |          |
| `placement`       | `"top" \| "top_left" \| "top_right" \| "bottom" \| "bottom_left" \| "bottom_right" \| "left" \| "right"` | `"bottom"` | Placement of the popup relative to the field.                                                                                                                                                                                                 |          |
| `anchorOffset`    | `number`                                                                                                 |            | Distance from the anchor to the popup. Defaults to 4 (medium) or 2 (small).                                                                                                                                                                   |          |
| `portalContainer` | `HTMLElement \| React.RefObject<HTMLElement>`                                                            |            | The container into which the popup is portaled. Defaults to the document body.                                                                                                                                                                |          |

### Combobox.List

| Name       | Type                                  | Default | Description                                                                                                                                                                                                                                   | Required |
| ---------- | ------------------------------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- |
| `children` | `(item, index) => React.ReactElement` |         | A render function called for each item that survives filtering. Return one `Combobox.Item` per item.                                                                                                                                          | Yes      |
| `css`      | `StitchesCss`                         |         | Apply styles directly to a component in a similar way how you would define inline styles. Wave uses [Stitches](https://stitches.dev/) under the hood with a fully-typed API and support for features like tokens, media queries, or variants. |          |

### Combobox.VirtualList

| Name           | Type                                        | Default   | Description                                                                                                                                                                                                                                   | Required |
| -------------- | ------------------------------------------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- |
| `children`     | `(item, virtualItem) => React.ReactElement` |           | A render function called for each visible row. Return one `Combobox.Item`; the virtualisation props are merged in automatically.                                                                                                              | Yes      |
| `estimateSize` | `number`                                    |           | Seed row height in pixels, measured per row. Defaults to the item height for the current size.                                                                                                                                                |          |
| `overscan`     | `number`                                    | `10`      | Number of rows rendered beyond the viewport on each side.                                                                                                                                                                                     |          |
| `maxHeight`    | `StitchesCss['maxHeight']`                  | `"25rem"` | Caps the list height. The popup bound still clips it on short viewports.                                                                                                                                                                      |          |
| `css`          | `StitchesCss`                               |           | Apply styles directly to a component in a similar way how you would define inline styles. Wave uses [Stitches](https://stitches.dev/) under the hood with a fully-typed API and support for features like tokens, media queries, or variants. |          |

### Combobox.Item

| Name           | Type                                                      | Default | Description                                                                                                                                                                                                                                                                                                                              | Required |
| -------------- | --------------------------------------------------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- |
| `value`        | `Value`                                                   |         | The value of the item. Can be a string or an object.                                                                                                                                                                                                                                                                                     | Yes      |
| `as`           | `keyof JSX.IntrinsicElements \| React.ComponentType<any>` | `div`   | Change the component to a different HTML tag or custom component. This will merge the original component props with the props of the supplied element/component and change the underlying DOM node.   For more details, read our [Composition](https://next.wave-design-system.pages.dev/get-started/composition.md#polymorphism) guide. |          |
| `css`          | `StitchesCss`                                             |         | Apply styles directly to a component in a similar way how you would define inline styles. Wave uses [Stitches](https://stitches.dev/) under the hood with a fully-typed API and support for features like tokens, media queries, or variants.                                                                                            |          |
| `isDisabled`   | `boolean`                                                 |         | When `true`, the item is disabled — it stays visible and filterable but can't be highlighted or selected.                                                                                                                                                                                                                                |          |
| `index`        | `number`                                                  |         | Explicit item index. Inferred from DOM order when omitted; provide it only in advanced virtualisation scenarios.                                                                                                                                                                                                                         |          |
| `startElement` | `React.ReactElement`                                      |         | `ReactElement` to render to the left of the item's children.                                                                                                                                                                                                                                                                             |          |
| `endElement`   | `React.ReactElement`                                      |         | `ReactElement` to render to the right of the item's children.                                                                                                                                                                                                                                                                            |          |
| `nativeButton` | `boolean`                                                 |         | Whether the item renders as a native `<button>`. Inferred from the `as` tag; set it explicitly when `as` is a custom component that renders a button.                                                                                                                                                                                    |          |

### Combobox.ItemIndicator

| Name  | Type                                                      | Default | Description                                                                                                                                                                                                                                                                                                                              | Required |
| ----- | --------------------------------------------------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- |
| `as`  | `keyof JSX.IntrinsicElements \| React.ComponentType<any>` | `div`   | Change the component to a different HTML tag or custom component. This will merge the original component props with the props of the supplied element/component and change the underlying DOM node.   For more details, read our [Composition](https://next.wave-design-system.pages.dev/get-started/composition.md#polymorphism) guide. |          |
| `css` | `StitchesCss`                                             |         | Apply styles directly to a component in a similar way how you would define inline styles. Wave uses [Stitches](https://stitches.dev/) under the hood with a fully-typed API and support for features like tokens, media queries, or variants.                                                                                            |          |

### Combobox.NoValueFound

| Name  | Type                                                      | Default | Description                                                                                                                                                                                                                                                                                                                              | Required |
| ----- | --------------------------------------------------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- |
| `as`  | `keyof JSX.IntrinsicElements \| React.ComponentType<any>` | `div`   | Change the component to a different HTML tag or custom component. This will merge the original component props with the props of the supplied element/component and change the underlying DOM node.   For more details, read our [Composition](https://next.wave-design-system.pages.dev/get-started/composition.md#polymorphism) guide. |          |
| `css` | `StitchesCss`                                             |         | Apply styles directly to a component in a similar way how you would define inline styles. Wave uses [Stitches](https://stitches.dev/) under the hood with a fully-typed API and support for features like tokens, media queries, or variants.                                                                                            |          |

### Combobox.Clear

| Name    | Type          | Default | Description                                                                                                                                                                                                                                   | Required |
| ------- | ------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- |
| `label` | `string`      |         | The `aria-label` of the button to use for screen readers.                                                                                                                                                                                     | Yes      |
| `css`   | `StitchesCss` |         | Apply styles directly to a component in a similar way how you would define inline styles. Wave uses [Stitches](https://stitches.dev/) under the hood with a fully-typed API and support for features like tokens, media queries, or variants. |          |

### 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.

```jsx
const { startsWith } = Combobox.useFilter();

<Combobox.Root items={items} filter={startsWith}>
  {/* … */}
</Combobox.Root>;
```
