---
language: "en"
title: "Combobox"
canonical: "https://uniaria.tenphi.me/components/combobox/"
source: "docs/components/combobox.mdx"
index: true
---

# Combobox

A combo box is a text input with a list of suggestions under it: typing
filters the options, and choosing one puts its text in the input and makes its
key the value. Use it instead of `<input list>` with a `<datalist>` when the
options need markup, groups or a styled popup, or when the value isn't the
text. Combobox is an [Autocomplete](/components/autocomplete/), a
[Listbox](/components/listbox/) behind a text input, with its list in a
[Popover](/components/popover/). Focus stays in the input the whole time; the
highlighted option is the input's `aria-activedescendant`.

It follows the WAI-ARIA [Combobox pattern](https://www.w3.org/WAI/ARIA/apg/patterns/combobox/),
as in its [list autocomplete example](https://www.w3.org/WAI/ARIA/apg/patterns/combobox/examples/combobox-autocomplete-list/).

## Usage

```tsx
import { combobox } from '@uniaria/core/combobox';
import { useBehavior } from '@uniaria/react';

const fruits = [
  { key: 'apple', textValue: 'Apple' },
  { key: 'banana', textValue: 'Banana' },
  { key: 'cherry', textValue: 'Cherry' },
];

export function FruitCombobox() {
  const fruit = useBehavior(combobox, { items: fruits });
  return (
    <div {...fruit.parts.root}>
      <label {...fruit.parts.label}>Fruit</label>
      <div {...fruit.parts.anchor}>
        <input {...fruit.parts.input} />
        <button {...fruit.parts.button}>
          <ChevronIcon aria-hidden />
        </button>
      </div>
      <div {...fruit.parts.popover}>
        <div {...fruit.parts.list}>
          {fruits.map((item) => (
            <div key={item.key} {...fruit.getItemProps('option', item.key)}>
              {item.textValue}
            </div>
          ))}
        </div>
        <p {...fruit.parts.empty}>No fruit matches</p>
      </div>
      <input {...fruit.parts.hiddenInput} name="fruit" />
    </div>
  );
}
```

```vue
<script setup lang="ts">
import { combobox } from '@uniaria/core/combobox';
import { useBehavior } from '@uniaria/vue';

const fruits = [
  { key: 'apple', textValue: 'Apple' },
  { key: 'banana', textValue: 'Banana' },
  { key: 'cherry', textValue: 'Cherry' },
];

const fruit = useBehavior(combobox, { items: fruits });
</script>

<template>
  <div v-bind="fruit.parts.root">
    <label v-bind="fruit.parts.label">Fruit</label>
    <div v-bind="fruit.parts.anchor">
      <input v-bind="fruit.parts.input" />
      <button v-bind="fruit.parts.button">
        <ChevronIcon aria-hidden="true" />
      </button>
    </div>
    <div v-bind="fruit.parts.popover">
      <div v-bind="fruit.parts.list">
        <div
          v-for="item in fruits"
          :key="item.key"
          v-bind="fruit.getItemProps('option', item.key)"
        >
          {{ item.textValue }}
        </div>
      </div>
      <p v-bind="fruit.parts.empty">No fruit matches</p>
    </div>
    <input v-bind="fruit.parts.hiddenInput" name="fruit" />
  </div>
</template>
```

```html
<uni-combobox>
  <div data-uni="combobox">
    <label data-element="Label">Fruit</label>
    <div data-element="Anchor">
      <input data-element="Input" />
      <button data-element="Button"><svg aria-hidden="true">…</svg></button>
    </div>
    <div data-element="Popover">
      <div data-element="List">
        <div data-element="Option" data-key="apple">Apple</div>
        <div data-element="Option" data-key="banana">Banana</div>
        <div data-element="Option" data-key="cherry">Cherry</div>
      </div>
      <p data-element="Empty">No fruit matches</p>
    </div>
    <input data-element="HiddenInput" name="fruit" />
  </div>
</uni-combobox>

<script type="module">
  import '@uniaria/elements/combobox';
</script>
```

Render every option, matching or not: UniAria hides the ones the typed text
leaves out. The options are Listbox's item parts, so keys, groups, option
labels, descriptions and [extras](/components/listbox/#extras-in-options), and items
read from the markup work as on [Listbox](/components/listbox/#items-and-keys).

Render the `root` part around everything, the popover included. Combobox
tracks focus within it: when focus leaves the root, the typed text settles and
the list closes. Without a root, the text never settles on blur and
`focuschange` never fires.

### Filtering

By default an option matches when its text contains the typed text, trimmed,
ignoring case and accents, in the field's language: `locale`, else the
nearest `lang` attribute, else the browser's. Group labels aren't searched; a
group with no match left is hidden with its label.

* **When it applies.** Only after the user types. The text a chosen option
  wrote filters nothing, so opening the list after a choice shows every
  option. After typing, opening the list shows the matches, or every option
  when nothing matches.
* **What it hides.** Options and groups left out get the `hidden` attribute,
  the arrow keys skip them, and they don't count in the option count. If your
  CSS sets `display` on options, keep `[hidden]` hiding them.
* **No matches.** `isEmpty` and `data-empty` are set. With an `empty` part,
  the list stays open and the part describes it; without one, the list closes
  until something matches.

Pass your own `filter` to match differently. It gets each option's text, the
trimmed input text, and the item as `items` holds it, so it can match more than
the text, such as keywords or a description:

```ts
const startsWith = (textValue: string, inputValue: string) =>
  textValue.toLowerCase().startsWith(inputValue.toLowerCase());

const byKeyword = (
  textValue: string,
  inputValue: string,
  item: { keywords: readonly string[] },
) => item.keywords.includes(inputValue.toLowerCase());
```

`filter: false` shows `items` as you give them, for filtering on a server from
`inputchange`:

```tsx
const [query, setQuery] = useState('');
const cities = useCitySearch(query); // your data
const city = useBehavior(combobox, {
  items: cities,
  filter: false,
  onInputChange: setQuery,
});
```

On the element, `filter` is a property: `element.filter = startsWith`.

### Choosing and settling

Typing changes only the text and reports `inputchange`. The value changes when
the user chooses an option, or when the text settles.

* **Choosing** an option, with a press or Enter on the highlighted one,
  selects it, writes its text into the input and closes the list. Focus stays
  in the input. Choosing the selected option again just closes the list.
* **The text settles** on Enter with nothing highlighted, and when focus
  leaves the root. In order:
  1. Text equal to an enabled option's text, ignoring case and accents,
     selects that option.
  2. After typing, a text that exactly one enabled option matches selects
     it: "kiw" selects Kiwi.
  3. Blank text clears the value.
  4. With `allowsCustomValue`, the trimmed text becomes the value.
  5. Otherwise the text goes back to the selected option's text, or to empty.

Enter with nothing highlighted isn't prevented, so a form around the combo box
submits with the settled value.

### Custom values

With `allowsCustomValue` (`allows-custom-value`), text that matches no option
becomes the value when it settles: typing "Mango smoothie" and pressing Enter
gives `value: ['Mango smoothie']`, which the hidden input submits. Text that
names an option still selects the option.

### Opening

| `popoverTrigger` | Opens the list                                            |
| ---------------- | --------------------------------------------------------- |
| `'input'`        | Typing non-blank text, the button, ArrowDown and ArrowUp. |
| `'focus'`        | The same, and focusing the input.                         |
| `'manual'`       | Only the button, ArrowDown and ArrowUp.                   |

### Forms

Put your `name`, and `form` if needed, on the `hiddenInput` part. UniAria makes
it `type="hidden"` and keeps its value at the selected key, or the custom
text, else empty.

* With `isRequired`, the visible input gets the native `required`: its text is
  empty exactly when the value is, so the browser blocks an empty submit and
  focuses the input.
* Resetting the form goes back to `defaultValue` and its text, even a
  `defaultValue` set after the combo box rendered, and `valueChange` reports
  it. A controlled combo box gets a `valueChange` asking for `defaultValue`,
  or without one the value it mounted with.

### Controlled value and text

```tsx
const [value, setValue] = useState<readonly string[]>([]);
const [inputValue, setInputValue] = useState('');
const fruit = useBehavior(combobox, {
  items: fruits,
  value,
  onValueChange: setValue,
  inputValue,
  onInputChange: setInputValue,
});

// A clear button of your own.
const clear = () => {
  setValue([]);
  setInputValue('');
};
```

```vue
<script setup lang="ts">
const value = ref<readonly string[]>([]);
const inputValue = ref('');
const fruit = useBehavior(combobox, () => ({
  items: fruits,
  value: value.value,
  onValueChange: (keys) => (value.value = keys),
  inputValue: inputValue.value,
  onInputChange: (text) => (inputValue.value = text ?? ''),
}));

// A clear button of your own.
function clear() {
  value.value = [];
  inputValue.value = '';
}
</script>
```

```js
const fruit = document.querySelector('uni-combobox');
fruit.value = [];
fruit.addEventListener('valuechange', (event) => {
  fruit.value = event.detail;
});
```

Setting the `value`, `inputValue` or `isOpen` property makes it controlled.
Set `defaultValue`, `defaultInputValue` or `defaultOpen` to let the element
keep its own. Reading the property gives the current value either way:
`inputValue` reads the input's text.

Without `inputValue` or `defaultInputValue`, the text starts as the selected
option's text.

### Translations

The button's name and the option count are English by default. Replace them
whole:

```tsx
const fruit = useBehavior(combobox, {
  items: fruits,
  buttonLabel: 'Vorschläge anzeigen',
  optionCountAnnouncement: (count) =>
    count === 1 ? '1 Option verfügbar' : `${count} Optionen verfügbar`,
});
```

On the element, `buttonLabel` is the `button-label` attribute and
`optionCountAnnouncement` a property.

### Virtualization

Combobox takes Listbox's virtualization props and passes them to its list. Put
the `scroller` part inside the popover, around the list, and render the
`renderedKeys` state as on [Listbox](/components/listbox/#virtualization). While the
user filters, `renderedKeys` holds only matching options. Give the popover a
width, such as `width: var(--anchor-width)`, since the rows have none of their
own.

### Option tooltips

Combobox takes Listbox's [option tooltip](/components/listbox/#option-tooltips): one
[Tooltip](/components/tooltip/#option-tooltips) with `shouldOpenOnlyWhenTruncated`
and `relation: 'none'`, which shows the whole text of a cut-off option. Pass
its `instance` as `optionTooltip`, or on the element name it by id with the
`option-tooltip` attribute, and render it inside the popover, after the list.

```tsx
const full = useBehavior(tooltip, {
  shouldOpenOnlyWhenTruncated: true,
  relation: 'none',
  positionArea: 'right',
});
const fruit = useBehavior(combobox, {
  items: fruits,
  optionTooltip: full.instance,
});
return (
  <div {...fruit.parts.root}>
    …
    <div {...fruit.parts.popover}>
      <div {...fruit.parts.list}>…</div>
      <div {...full.parts.root}>
        <span {...full.parts.text} />
      </div>
    </div>
  </div>
);
```

```vue
<script setup lang="ts">
const full = useBehavior(tooltip, {
  shouldOpenOnlyWhenTruncated: true,
  relation: 'none',
  positionArea: 'right',
});
const fruit = useBehavior(combobox, {
  items: fruits,
  optionTooltip: full.instance,
});
</script>

<template>
  <div v-bind="fruit.parts.root">
    …
    <div v-bind="fruit.parts.popover">
      <div v-bind="fruit.parts.list">…</div>
      <div v-bind="full.parts.root">
        <span v-bind="full.parts.text" />
      </div>
    </div>
  </div>
</template>
```

```html
<uni-combobox option-tooltip="fruit-full">
  <div data-uni="combobox">
    …
    <div data-element="Popover">
      <div data-element="List">…</div>
      <uni-tooltip
        id="fruit-full"
        open-only-when-truncated
        relation="none"
        position-area="right"
      >
        <div data-uni="tooltip"><span data-element="Text"></span></div>
      </uni-tooltip>
    </div>
  </div>
</uni-combobox>
```

It follows the visible highlight, which the keys move, and the pointer. Typing
or editing the text closes it until a key moves the highlight again.

Combobox takes no tooltip for its input: an input scrolls its text instead of
cutting it.

## Anatomy

| Part          | Element                    | Notes                                                                                                                                                                                                                                                                           |
| ------------- | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `root`        | any, usually `div`         | Around every other part. Focus leaving it settles the text and closes the list.                                                                                                                                                                                                 |
| `label`       | `label` by default         | Names the input and the list. UniAria writes its `for`, so a click focuses the input.                                                                                                                                                                                           |
| `anchor`      | any, usually `div`         | Optional. The box around the input and the button: the popover is placed against it and `--anchor-width` is its width. Without it, the input. Extras beside the input, such as a count or a button of yours, go in it too, as a prefix or a suffix goes in TextField's wrapper. |
| `input`       | `input`                    | The text. `type` is yours (`text` or `search`); UniAria writes the `value` property.                                                                                                                                                                                            |
| `button`      | `button`                   | Optional. Opens and closes the list without taking focus. Its icon is yours, hidden with `aria-hidden`.                                                                                                                                                                         |
| `popover`     | any, usually `div`         | Always rendered, after the input. Hidden while closed.                                                                                                                                                                                                                          |
| `arrow`       | any, inside the popover    | Optional. Points at the anchor.                                                                                                                                                                                                                                                 |
| `scroller`    | any, usually `div`         | Only when virtualized: the box that scrolls, around the list.                                                                                                                                                                                                                   |
| `list`        | any, usually `div` or `ul` | Inside the popover. Holds the options and groups.                                                                                                                                                                                                                               |
| `empty`       | any                        | Optional, inside the popover and outside the list. Shown while no option matches, and keeps the list open then.                                                                                                                                                                 |
| `description` | any                        | Optional. Describes the input.                                                                                                                                                                                                                                                  |
| `error`       | any                        | Optional. Shown and announced while `isInvalid`.                                                                                                                                                                                                                                |
| `errorText`   | any, inside `error`        | Optional. In a [Form](/components/form/), UniAria writes the first error's text here; leave it out to render errors yourself.                                                                                                                                                   |
| `hiddenInput` | `input`                    | Optional. Submits the value; UniAria makes it `type="hidden"` and writes its value.                                                                                                                                                                                             |

A part keeps an `id` you give it, and everything that refers to the part
follows it; UniAria writes an `id` only on a part that has none (see
[Ids](/guide/accessibility/#ids)).

The item parts are Listbox's: `option`, `optionLabel`, `optionDescription`,
`optionExtras`, `group` and `groupLabel`, inside the list and keyed by the
item's key (see [Listbox](/components/listbox/#anatomy)). They carry no `tabindex`,
since they never take focus.

## Props

| Prop                                                                            | Attribute                           | Type                                                                        | Default                       | Description                                                                                                                                                           |
| ------------------------------------------------------------------------------- | ----------------------------------- | --------------------------------------------------------------------------- | ----------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `items`, `getKey`, `getTextValue`, `isItemDisabled`, `getChildren`              | — (properties, or markup)           |                                                                             |                               | Listbox's: every option, unfiltered, and how to read them. `getTextValue` is also what the filter matches and what the input shows once chosen.                       |
| `disabledKeys`                                                                  | `disabled-keys` (JSON)              | `readonly string[]`                                                         | `[]`                          | Disabled options, by key. They're filtered like the others.                                                                                                           |
| `value` / `defaultValue`                                                        | — (properties)                      | `readonly string[]`                                                         | `[]`                          | The selected key, or the custom text: at most one. Changes only when the text settles, never per keystroke.                                                           |
| `inputValue` / `defaultInputValue`                                              | — (properties)                      | `string`                                                                    | the selected option's text    | The input's text. Changes are reported by `inputchange`.                                                                                                              |
| `isOpen` / `defaultOpen`                                                        | — / `default-open`                  | `boolean`                                                                   | `false`                       | Whether the list is open.                                                                                                                                             |
| `popoverTrigger`                                                                | `popover-trigger`                   | `'input' \| 'focus' \| 'manual'`                                            | `'input'`                     | What opens the list besides the button and the arrows.                                                                                                                |
| `filter`                                                                        | — (property)                        | `((textValue: string, inputValue: string, item: Item) => boolean) \| false` | contains                      | Which options match the typed text. `false` shows every item.                                                                                                         |
| `allowsCustomValue`                                                             | `allows-custom-value`               | `boolean`                                                                   | `false`                       | Text that matches no option becomes the value when it settles.                                                                                                        |
| `isDisabled`                                                                    | `disabled`                          | `boolean`                                                                   | `false`                       | The input and the button are natively disabled.                                                                                                                       |
| `isReadOnly`                                                                    | `readonly`                          | `boolean`                                                                   | `false`                       | The input is natively read-only. The list opens from the button and the arrows and the highlight moves, but nothing is selected.                                      |
| `isRequired`                                                                    | `required`                          | `boolean`                                                                   | `false`                       | `aria-required` and the native `required` on the input.                                                                                                               |
| `isInvalid`                                                                     | `invalid`                           | `boolean`                                                                   | —                             | `aria-invalid` on the input, and shows the `error` part. Unset in a [Form](/components/form/), the form's errors decide it.                                           |
| `shouldFocusWrap`                                                               | `focus-wrap`                        | `boolean`                                                                   | `false`                       | The arrows wrap from the last option to the first.                                                                                                                    |
| `positionArea`                                                                  | `position-area`                     | `PositionArea`                                                              | `'block-end span-inline-end'` | Where the popover goes: below the field, aligned to its start edge. See [Popover](/components/popover/).                                                              |
| `positionTryFallbacks`                                                          | `position-try-fallbacks`            | `string`                                                                    | `'flip-block, flip-inline'`   | Where it goes when that doesn't fit.                                                                                                                                  |
| `locale`                                                                        | `locale`                            | `string`                                                                    | the nearest `lang`            | The default filter's language. Without a `lang`, the browser's.                                                                                                       |
| `buttonLabel`                                                                   | `button-label`                      | `string`                                                                    | `'Show suggestions'`          | The button's own name, read before the label: "Show suggestions Fruit".                                                                                               |
| `optionCountAnnouncement`                                                       | — (property)                        | `(count: number) => string`                                                 | `'N options available'`       | The announcement of the option count; `'1 option available'` for one.                                                                                                 |
| `inputId`                                                                       | —                                   | `string`                                                                    | generated                     | The input's id while it has none of its own.                                                                                                                          |
| `listId`                                                                        | —                                   | `string`                                                                    | generated                     | The list's id while it has none of its own. Option ids are made from the list's id.                                                                                   |
| `labelElementType`                                                              | — (the label's tag)                 | `string`                                                                    | `'label'`                     | The tag you render as the label. Only a `label` gets `for`.                                                                                                           |
| `labelledBy`                                                                    | `labelled-by`                       | `string`                                                                    | —                             | Ids of more elements that name the input and the list.                                                                                                                |
| `describedBy`                                                                   | `described-by`                      | `string`                                                                    | —                             | Ids of more elements that describe the input.                                                                                                                         |
| `virtualizer`, `itemSize`, `shouldMeasureItems`, `overscan`, `initialItemCount` | as on Listbox                       |                                                                             |                               | Listbox's virtualization, passed to the list.                                                                                                                         |
| `optionTooltip`                                                                 | `option-tooltip` (the tooltip's id) | a Tooltip's instance                                                        | —                             | One [Tooltip](/components/tooltip/) for every option, following the highlight (see [Option tooltips](#option-tooltips)). On the element, only the attribute links it. |

In a [Form](/components/form/), Combobox also takes `name`, `form`, `validate`,
`validationTrigger`, `validationDelay`, `validationMessages`, `dependsOn`,
`isEqual` and `shouldPreserve`: see [Field props for the
form](/components/form/#field-props-for-the-form).

The list always highlights options on hover and is vertical; the popover is
always non-modal, dismissable, and leaves focus in the input when it opens.
Development builds warn when the input has no name: no `label` part, no
`labelledBy` and no `aria-label` on the input.

## Events

| Event                | Callback prop          | Detail                                                     | When                                                                                                                        |
| -------------------- | ---------------------- | ---------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| `valuechange`        | `onValueChange`        | `readonly string[]`                                        | The value settled to something new: an option chosen, or the text settling. Never per keystroke, never for a value you set. |
| `inputchange`        | `onInputChange`        | `string`                                                   | The text changed: typing, pasting, a chosen option's text written in, or the text going back as it settles.                 |
| `openchange`         | `onOpenChange`         | `boolean`                                                  | The user opened or closed the list. Once per change, never for an `isOpen` you set.                                         |
| `focuschange`        | `onFocusChange`        | `boolean`                                                  | Focus entered or left the root. The input, the button and the popup count as one.                                           |
| `renderedkeyschange` | `onRenderedKeysChange` | `{ keys: readonly string[]; sections: readonly string[] }` | The set of options to render changed. Only virtualized lists need it.                                                       |

## Actions

| Action    | What it does                                                                                                                                                                             |
| --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `open()`  | Opens the list, as a press on the button does, without moving focus: the selected option is highlighted, and every option shows when the text matches none. Does nothing while disabled. |
| `close()` | Closes the list and leaves the text as it is.                                                                                                                                            |

In React and Vue they are on the handle (`fruit.actions.open()`); on the
custom element they are methods
(`document.querySelector('uni-combobox').open()`).
While `isOpen` is controlled, each only reports `openChange` and leaves the
change to you.

## State

| Field                                                 | Type                | Meaning                                                        |
| ----------------------------------------------------- | ------------------- | -------------------------------------------------------------- |
| `value`                                               | `readonly string[]` | The selected key, or the custom text.                          |
| `inputValue`                                          | `string`            | The input's text.                                              |
| `isOpen`                                              | `boolean`           | The list is open.                                              |
| `focusedKey`                                          | `string \| null`    | The highlighted option, or `null`. Always `null` while closed. |
| `isEmpty`                                             | `boolean`           | No option matches the text, or there are no items.             |
| `isDisabled`, `isReadOnly`, `isRequired`, `isInvalid` | `boolean`           | The props of the same names.                                   |
| `isErrorShown`                                        | `boolean`           | The `error` part is shown: invalid and not disabled.           |
| `isFocusWithin`                                       | `boolean`           | Focus is in the root.                                          |
| `isFocusVisible`                                      | `boolean`           | That focus came from the keyboard.                             |
| `isVirtualized`, `renderedKeys`, `renderedSections`   |                     | Listbox's virtualization state.                                |

Select what you render, and the component renders again only when it changes:

```tsx
const inputValue = useBehaviorState(fruit, (state) => state.inputValue);
```

On the custom element, read `element.instance.getState()`. See
[State](/guide/state/).

## Styling

| Attribute                                                         | On               | When                                                                                    |
| ----------------------------------------------------------------- | ---------------- | --------------------------------------------------------------------------------------- |
| `data-uni="combobox"`                                             | root             | Always.                                                                                 |
| `data-element`                                                    | every other part | Always.                                                                                 |
| `data-open`                                                       | named parts      | The list is open.                                                                       |
| `data-entering`, `data-exiting`                                   | named parts      | The first frames after opening; closing, until your exit animations on the popover end. |
| `data-empty`                                                      | named parts      | No option matches the text.                                                             |
| `data-disabled`, `data-readonly`, `data-required`, `data-invalid` | named parts      | The combo box is disabled, read-only, required or invalid.                              |
| `data-focus-within`, `data-focus-visible`                         | named parts      | Focus is in the root, and from the keyboard.                                            |
| `data-pressed`, `data-hovered`                                    | button           | As on [Button](/components/button/).                                                    |
| `data-placement`                                                  | popover, arrow   | The side the popover opened on: `top`, `bottom`, `left` or `right`.                     |
| `data-focused`                                                    | option           | The option is highlighted, by the keys or the mouse.                                    |
| `data-focus-visible`                                              | option           | The highlight came from the keys.                                                       |
| `data-selected`, `data-hovered`, `data-pressed`, `data-disabled`  | option           | Listbox's.                                                                              |
| `hidden`                                                          | option, group    | The typed text leaves it out.                                                           |

"Named parts" are every part except `scroller` and `hiddenInput`. State
attributes are present or absent, never `"false"`. The input also has the
native `required`, `readonly` and `disabled`, for `:required`, `:read-only` and
`:disabled`.

The popover gets Popover's custom properties, as on
[Select](/components/select/#styling): `--anchor-width` (the anchor's width, else the
input's), `--available-width` and `--available-height`. Use them on the
popover:

```css
[data-uni='combobox'] [data-element='Popover'] {
  min-width: var(--anchor-width);
  max-height: min(15rem, var(--available-height));
  margin: 4px 0; /* the gap from the field */
  overflow: auto;
}
```

### Default styles

The optional [`@uniaria/styles`](/guide/default-styles/) package styles
every part, as in the demo above, whose CSS tab holds only the page's layout.
The anchor is the field's box, as [TextField](/components/text-field/)'s wrapper,
around the input and the button, a quiet 24-pixel button that draws a chevron,
so leave your icon out. The box keeps its ring while the keys move the
highlight, since focus stays in the input. The popover, its motion and the
options are [Select](/components/select/#default-styles)'s: an option's text takes one
line and ends in an ellipsis, so pair the list with an
[option tooltip](#option-tooltips), and its extras sit before the check.

## Keyboard

On the input. Keys with Ctrl or Meta, and every key while an input method is
composing, stay with the browser.

| Key                                | Closed                                                                                                                 | Open                                                                            |
| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- |
| ArrowDown                          | Opens and highlights the selected option, else the first.                                                              | Highlights the next option; from none, the first. Wraps with `shouldFocusWrap`. |
| ArrowUp                            | Opens and highlights the selected option, else the last.                                                               | Highlights the previous option; from none, the last.                            |
| Alt+ArrowDown                      | Opens with nothing highlighted.                                                                                        | Nothing.                                                                        |
| Alt+ArrowUp                        | Nothing.                                                                                                               | Closes, keeping the text.                                                       |
| PageDown, PageUp                   | The browser's.                                                                                                         | With an option highlighted, moves the highlight by the list's visible height.   |
| Home, End, ArrowLeft, ArrowRight   | The browser's: the caret moves.                                                                                        | The caret moves and the highlight clears.                                       |
| Enter                              | Settles the text. Not prevented, so a form submits.                                                                    | Chooses the highlighted option. With none, settles the text and closes.         |
| Escape                             | Puts back the settled text if the user changed it. Otherwise not handled, so it reaches a dialog around the combo box. | Closes, keeping the text.                                                       |
| Tab, Shift+Tab                     | The browser's. The text settles as focus leaves.                                                                       | The same. The list closes without choosing the highlighted option.              |
| Typing, Backspace, Delete, pasting | Edits the text; with `popoverTrigger` `'input'` or `'focus'` and non-blank text, opens the list.                       | Edits the text. The filter follows it and the highlight clears.                 |

## Accessibility

* **Focus stays in the input.** The highlighted option is the input's
  `aria-activedescendant`, written only once the option's element exists. The
  list and options never take focus, and a press on an option leaves focus in
  the input. A highlight moved by the keys scrolls into view, which browsers
  don't do for `aria-activedescendant`.
* **Nothing is highlighted while typing.** Enter then settles the text, and
  ArrowDown starts highlighting. NVDA stops reading typed characters while an
  active descendant is set.
* **The option count** is announced politely when the list opens and when
  typing changes it while open: "3 options available". Matching disabled
  options count too. Translate it with `optionCountAnnouncement`.
* **Spoken output.** The input reads "combobox, Fruit, has popup listbox, not
  expanded, autocomplete in list", and while an option is highlighted, "active
  descendant Banana". Filtered-out options aren't read.
* **Option tooltips add nothing.** With `relation: 'none'` the tooltip is
  hidden from assistive technology, and the option's whole text is read from
  the option already, cut off or not.
* **The button** isn't a tab stop and never takes focus; a press on it
  focuses the input, which brings up the on-screen keyboard on touch screens.
  It reads "button, Show suggestions Fruit".
* **The label** is a `<label for>`, so clicking it focuses the input.
* **The popup is non-modal.** The page stays exposed to assistive technology,
  Tab leaves the combo box as usual, and one press outside closes the list
  and acts on what it hit.
* **The root part** is needed: focus leaving it is what settles the text.

## Compared with other libraries

* **No highlight while typing**, as in React Aria, Zag and the APG's list
  example. Cube UI Kit highlights the first match, which makes a custom value
  that is part of an option's text impossible to enter with Enter.
* **Enter with nothing highlighted settles the text** and lets a form submit.
  The APG's example always prevents Enter.
* **Blur selects an exact or only match**, as Cube UI Kit does. React Aria
  reverts the text, or keeps it as a custom value.
* **A custom value is the value** (`value: ['Mango smoothie']`), which a form
  submits. React Aria keeps the value empty and the text in `inputValue`.
* **Home and End move the caret**, as the APG says. React Aria moves the
  highlight while the list is open.
* **Escape on a closed combo box** puts back the settled text, else reaches a
  dialog around it. The APG clears the text.
* **Tab doesn't choose the highlighted option.** React Aria and the APG's
  example choose it.
* **The page stays exposed** while the list is open. React Aria hides
  everything but the field and the popup from assistive technology.
* **Compared with `<datalist>`**, options can hold markup and groups, the
  value can differ from the text, and filtering and keys work the same in
  every browser.
