# Autocomplete

> Combobox and select in one component: one value or several, remote options, a create row and virtual lists, with every part replaceable by your design system's.

The slotsmith Autocomplete is a React combobox and select in one component: it filters as you type, or works as a plain select with typeahead. It takes one value or several, remote options, a create row and virtual lists. Every part it renders is a slot you can replace.

## Overview

The demo below uses the fallbacks only. Open a picker with the mouse or with <kbd>↓</kbd>, type to filter, and pick with <kbd>Enter</kbd>. The topics picker keeps several values as tags; its last topic is archived and cannot be picked.

```tsx
import { useId, useState } from "react";
import { Autocomplete, type OptionValue } from "slotsmith/autocomplete";
import { countries, topics, type Country } from "../shared/options";

export default function Overview() {
  const countryLabel = useId();
  const topicsLabel = useId();
  const [country, setCountry] = useState<OptionValue | null>(null);
  const [topicIds, setTopicIds] = useState<OptionValue[]>(["accessibility", "forms"]);
  const countryName = countries.find((entry) => entry.code === country)?.name ?? "none";
  const topicNames = topics.filter((topic) => topicIds.includes(topic.id)).map((topic) => topic.label);

  return (
    <div style={{ display: "grid", gap: "1rem", maxWidth: 360 }}>
      <div>
        <p id={countryLabel}>Country</p>
        {/* One value, filtered as you type. */}
        <Autocomplete<Country>
          options={countries}
          getOptionValue={(entry) => entry.code}
          getOptionLabel={(entry) => entry.name}
          value={country}
          onChange={setCountry}
          aria-labelledby={countryLabel}
        />
      </div>
      <div>
        <p id={topicsLabel}>Topics</p>
        {/* Several values, shown as tags; the archived topic cannot be picked. */}
        <Autocomplete
          multiple
          options={topics}
          optionDisabled={(topic) => !!topic.archived}
          value={topicIds}
          onChange={setTopicIds}
          aria-labelledby={topicsLabel}
        />
      </div>
      <div>
        <p>Country: {countryName}</p>
        <p>Topics: {topicNames.length ? topicNames.join(", ") : "none"}</p>
      </div>
    </div>
  );
}
```

Each feature has a guide with its own live demo: [select or combobox](https://slotsmith.dev/components/autocomplete/guides/select-or-combobox/) with auto highlight, [search in the trigger](https://slotsmith.dev/components/autocomplete/guides/search-in-trigger/), [multiple values](https://slotsmith.dev/components/autocomplete/guides/multiple/), [groups and separators](https://slotsmith.dev/components/autocomplete/guides/groups/), [remote options](https://slotsmith.dev/components/autocomplete/guides/remote-options/) with debounce, cancellation and paging, the [loading, empty and error states](https://slotsmith.dev/components/autocomplete/guides/states/), [validation](https://slotsmith.dev/components/autocomplete/guides/validation/), [creatable options](https://slotsmith.dev/components/autocomplete/guides/creatable/), [virtual lists](https://slotsmith.dev/components/autocomplete/guides/virtual/) for long lists, and [the compound parts](https://slotsmith.dev/components/autocomplete/guides/compound-parts/) for your own layout.

## Install

```bash
npm i slotsmith
```

`VirtualAutocomplete` from `slotsmith/virtual` also needs `@tanstack/react-virtual`.

```ts
import "slotsmith/autocomplete.css";
```

Import the stylesheet once, at your app's entry. `slotsmith/styles.css` holds every component's styles instead.

## Quick start

Pass `options`, `value` and `onChange`, and name the combobox with `aria-label` or `aria-labelledby`. Options with `value` and `label` (or `id` and `name`) need no further props.

```tsx
import { useState } from "react";
import { Autocomplete, type OptionValue } from "slotsmith/autocomplete";
import "slotsmith/autocomplete.css";

// `value` and `label` are read by default; other shapes take getOptionValue and getOptionLabel.
const fruits = [
  { value: "apple", label: "Apple" },
  { value: "banana", label: "Banana" },
  { value: "cherry", label: "Cherry" },
  { value: "fig", label: "Fig" },
  { value: "mango", label: "Mango" },
  { value: "pear", label: "Pear" },
];

export default function QuickStart() {
  const [fruit, setFruit] = useState<OptionValue | null>(null);
  // aria-label names the combobox; style and className go on the root element.
  return <Autocomplete options={fruits} value={fruit} onChange={setFruit} aria-label="Fruit" style={{ maxWidth: 320 }} />;
}
```

## Guides

- [Select or combobox](https://slotsmith.dev/components/autocomplete/guides/select-or-combobox/): Use the slotsmith Autocomplete as a combobox that filters as you type, or as a select with typeahead, by switching one prop.
- [Search in the trigger](https://slotsmith.dev/components/autocomplete/guides/search-in-trigger/): Put the slotsmith Autocomplete's search box inside the trigger with searchIn="trigger": an editable combobox, its single and multiple modes, keys and IME.
- [Multiple values](https://slotsmith.dev/components/autocomplete/guides/multiple/): Pick several values with the slotsmith Autocomplete: tags in the trigger, Backspace to remove the last one, and an overflow count.
- [Groups and separators](https://slotsmith.dev/components/autocomplete/guides/groups/): Group the slotsmith Autocomplete's options under labels with getOptionGroup: group order, groups that span pages, virtual lists and the ARIA roles.
- [Remote options](https://slotsmith.dev/components/autocomplete/guides/remote-options/): Load the slotsmith Autocomplete's options from a server with useAsyncOptions: debounced, cancelled when superseded, and paged.
- [Loading, empty and error states](https://slotsmith.dev/components/autocomplete/guides/states/): What the slotsmith Autocomplete shows instead of options: the loading, empty, minimum-characters and error rows, and how to change them.
- [Validation](https://slotsmith.dev/components/autocomplete/guides/validation/): Show a failed check on the slotsmith Autocomplete: the invalid prop, an error message tied to the combobox, and the label and message styles your app adds.
- [Creatable options](https://slotsmith.dev/components/autocomplete/guides/creatable/): Let people add a missing option from the slotsmith Autocomplete's search: the create row, onCreate, and a pending create.
- [Virtual lists](https://slotsmith.dev/components/autocomplete/guides/virtual/): Show thousands of options in the slotsmith Autocomplete with the virtual prop, which renders only the options in view.
- [Compound parts](https://slotsmith.dev/components/autocomplete/guides/compound-parts/): Lay out the slotsmith Autocomplete yourself with Autocomplete.Provider and its parts, read its state from the context, or use the useAutocomplete hook.

## Reference

The [API page](https://slotsmith.dev/components/autocomplete/api/) is generated from the library's source, so it lists every slot, prop and label the installed version has.

- [Slots](https://slotsmith.dev/components/autocomplete/api/#slots): Every part you can replace, the props it receives and its fallback.
- [Props](https://slotsmith.dev/components/autocomplete/api/#props): Every prop, grouped, with its type and default.
- [Labels & locales](https://slotsmith.dev/components/autocomplete/api/#labels): Every string it shows or announces, and how to translate them.
- [Styling](https://slotsmith.dev/components/autocomplete/api/#styling): Its class names and the tokens its stylesheet reads.
- [Adapters](https://slotsmith.dev/components/autocomplete/adapters/): Ready slot maps for shadcn/ui, MUI, Chakra, Ant Design and Radix Themes.

## Adapters

The autocomplete works with any design system. Each slot takes a component, so a library's own popover surface, input, tag and menu item can replace the fallbacks. The [Adapters page](https://slotsmith.dev/components/autocomplete/adapters/) has ready slot maps for shadcn/ui, MUI, Chakra, Ant Design and Radix Themes, as examples of well-known libraries; the same approach works with your own.

## Accessibility

The autocomplete follows the ARIA combobox pattern, with the listbox in a popup:

- **The trigger** is the element with `role="combobox"`. It is in the tab order, and carries `aria-expanded`, `aria-haspopup="listbox"` and, while open, `aria-controls` pointing at the list. The naming and validation attributes you pass to `Autocomplete` (`aria-label`, `aria-labelledby`, `aria-describedby`, `aria-invalid`, `aria-required` and the like, plus `id`) go to this element, not to the wrapper.
- **The search box** gets focus when the popup opens. It has `role="searchbox"`, `aria-autocomplete="list"`, and is named by the [`search`](https://slotsmith.dev/components/autocomplete/api/#label-search) label. See [select or combobox](https://slotsmith.dev/components/autocomplete/guides/select-or-combobox/).
- **Search in the trigger.** With `searchIn="trigger"` the combobox is an input inside the trigger, the WAI-ARIA 1.2 editable combobox. It carries `role="combobox"`, `aria-autocomplete="list"`, `aria-expanded`, `aria-activedescendant` and the naming props, keeps focus while the list is open, and a `<label for>` names it. The chevron is a button outside the tab order, named by the [`toggle`](https://slotsmith.dev/components/autocomplete/api/#label-toggle) label. See [search in the trigger](https://slotsmith.dev/components/autocomplete/guides/search-in-trigger/).
- **The highlight** is announced through `aria-activedescendant`: on the search box, or on the trigger itself when `searchable` is `false`, so focus never leaves the field while you move through the options.
- **The list** has `role="listbox"`, with `aria-multiselectable="true"` in multiple mode and `aria-busy="true"` while `loading` is set: with `useAsyncOptions`, while each search loads its first page, even when the last options still show. Loading more at the end of the list does not set it. Each option has `role="option"` and `aria-selected`; an option ruled out by `optionDisabled` has `aria-disabled="true"` and the arrow keys skip it. See [multiple values](https://slotsmith.dev/components/autocomplete/guides/multiple/).
- **Groups.** With `getOptionGroup`, each group has `role="group"` and is named by its label through `aria-labelledby`. The line between groups is hidden from assistive technology. See [groups and separators](https://slotsmith.dev/components/autocomplete/guides/groups/).
- **Invalid values.** `invalid` sets `aria-invalid="true"` on the combobox, never on the popup's search box. Point it at your error message with `aria-describedby`. See [validation](https://slotsmith.dev/components/autocomplete/guides/validation/).
- **Result counts** are read out: a visually hidden `role="status"` region (`aria-live="polite"`) says "12 results" as the list changes, from the [`results`](https://slotsmith.dev/components/autocomplete/api/#label-results) label. It is empty while the popup is closed or shows a status row. See [remote options](https://slotsmith.dev/components/autocomplete/guides/remote-options/).
- **Tags and clearing.** Each tag's remove button is named by the [`remove`](https://slotsmith.dev/components/autocomplete/api/#label-remove) label ("Remove Forms"), and the clear button by [`clear`](https://slotsmith.dev/components/autocomplete/api/#label-clear). Spinners are hidden from assistive technology. See [the states](https://slotsmith.dev/components/autocomplete/guides/states/).

With focus on the closed trigger (the default, `searchIn="popup"`):

| Keys | Action |
| --- | --- |
| ↓, Enter, Space | Open the popup and highlight the first option. |
| ↑ | Open the popup and highlight the last option. |
| A–Z | Open the popup. With searchable set to false, also jump to the first option that starts with the typed letters. |

With the popup open:

| Keys | Action |
| --- | --- |
| ↓, ↑ | Move the highlight, skipping disabled options. It stops at the ends unless loop is set. |
| Home, End | Highlight the first or the last option. |
| PgDn, PgUp | Move the highlight ten options. |
| Enter | Pick the highlighted option. With nothing highlighted and a create row offered, create. |
| Esc | Close, and move focus back to the trigger. |
| Tab | Close, and move focus on as usual. |
| Backspace | In multiple mode, with the search box empty, remove the last tag. |
| A–Z | With searchable set to false: jump to the next option that starts with the typed letters. |

With `searchIn="trigger"` the input keeps the caret, so <kbd>Space</kbd>, <kbd>Home</kbd> and <kbd>End</kbd> stay in the text:

| Keys | Action |
| --- | --- |
| A–Z | Type into the search. The list opens and narrows. |
| ↓, ↑ | Open the list on the first or the last option; with the list open, move the highlight. |
| Alt+↓, Alt+↑ | Open the list without moving the highlight, or close it. |
| PgDn, PgUp | With the list open: move the highlight ten options. |
| Enter | Pick the highlighted option. Nothing is picked while an input method is composing. |
| Esc | Close the list and clear the search. In single mode the picked label comes back. |
| Backspace | In multiple mode, with the input empty, remove the last tag. |

Picking an option in single mode closes the popup and returns focus to the trigger; in multiple mode the popup stays open. A pointer press outside the control closes it. Every label can be translated or replaced; see [Labels & locales](https://slotsmith.dev/components/autocomplete/api/#labels).

## Limitations

- It does not fetch. `Autocomplete` renders the options you pass; [`useAsyncOptions`](https://slotsmith.dev/components/autocomplete/guides/remote-options/) calls your own `load` function.
- No inline completion: the input never fills in the rest of the top match as you type. For that, or for a command palette, build on the [`useAutocomplete`](https://slotsmith.dev/components/autocomplete/guides/compound-parts/) hook.
- Groups are one level deep and take their order from the options; there are no nested groups. See [groups and separators](https://slotsmith.dev/components/autocomplete/guides/groups/).
- The "Load more" and "Retry" buttons in the list are reached with the pointer only: the keyboard stays in the search.
- An option's label is a string, because the trigger, the filter and typeahead read it. A richer row goes in the `OptionLabel` slot. See [remote options](https://slotsmith.dev/components/autocomplete/guides/remote-options/).
- The popup renders in place by default, with `position: fixed`, so it escapes `overflow: hidden` and makes up for an ancestor with a `transform`. Pass [`portal`](https://slotsmith.dev/components/autocomplete/api/#props) to render it in `document.body` or an element of your own; set the `--ss-*` tokens and the dark class on `:root` or that element, which the popup then inherits from.

## FAQ

### Is the autocomplete a select or a combobox?

Both. By default it is a combobox: the popup has a search box that filters the options as you type. With searchable set to false it is a select: no search box, and typing a letter jumps to the first option that starts with it. See https://slotsmith.dev/components/autocomplete/guides/select-or-combobox/

### Does the autocomplete fetch options itself?

No. It renders the options you pass. For remote options, the useAsyncOptions hook calls your load function with the query, the page and an AbortSignal, and returns the props to spread onto the component: options, loading, error, paging and filter set to false. See https://slotsmith.dev/components/autocomplete/guides/remote-options/

### How do I give the autocomplete an accessible name?

Pass aria-label or aria-labelledby to Autocomplete. The naming and validation attributes (id, title, aria-label, aria-labelledby, aria-describedby, aria-details, aria-errormessage, aria-invalid and aria-required) go to the element with role combobox, not to the wrapper. See https://slotsmith.dev/components/autocomplete/#accessibility

### Can the autocomplete submit with a plain HTML form?

Yes. Give it a name and it renders one hidden input per selected value. onBlur fires once when focus leaves the whole control, which is when form libraries mark a field as touched. See https://slotsmith.dev/components/autocomplete/api/#prop-name

### Can it show thousands of options?

Yes, with VirtualAutocomplete from slotsmith/virtual, which renders only the options in view. It needs @tanstack/react-virtual ^3.10.0 installed and takes the same props as Autocomplete, plus virtual. See https://slotsmith.dev/components/autocomplete/guides/virtual/
