# Autocomplete API

> Props, slots, labels and styling of the slotsmith Autocomplete.

## Slots

`Autocomplete` has 22 slots. Pass a replacement for any of them in `components`; every slot you leave out keeps its fallback. Element slots receive DOM props, so a library's own element fits as it is; widget slots receive named props and usually need a few lines of adapter.

| Slot | Kind | Props type | Description |
| --- | --- | --- | --- |
| `Root` | element | `AutocompleteRootSlotProps` | The element the popup is positioned against. Carries `data-open`, `data-disabled`, `data-invalid` and `data-empty`. |
| `Trigger` | element | `AutocompleteTriggerSlotProps` | The control that opens the list. It carries `role="combobox"` and is a `<div>` rather than a `<button>`, because the tag and clear controls inside it are buttons and HTML forbids nesting them. With `invalid` set it carries `aria-invalid="true"` and `data-invalid`. A skin built on a library component with an error prop (MUI's `error`, Ant Design's `status`) reads either attribute and passes the prop on. With `searchIn="trigger"` it is the field around the `TriggerInput` instead: it carries `data-search-in="trigger"`, no role and no tab stop, and `data-open`, `data-disabled`, `data-invalid` and `data-empty` for styling, while `aria-invalid` moves to the input. Draw the focus ring with `:focus-within`, since focus sits on the input inside it. |
| `TriggerInput` | element | `AutocompleteTriggerInputSlotProps` | The input inside the trigger when `searchIn` is `"trigger"`. It is the combobox: it carries `role="combobox"`, the aria wiring, the value and the key handler, and keeps focus while the list is open. Render a borderless input that grows to fill the row; the `Trigger` around it draws the field. |
| `Toggle` | element | `AutocompleteToggleSlotProps` | The button that opens and closes the list when `searchIn` is `"trigger"`. It is left out of the tab order (`tabIndex={-1}`), carries `aria-label`, `aria-expanded` and `data-open`, and holds the `Indicator` as `children`. Pressing it never takes focus from the input. |
| `Value` | widget | `AutocompleteValueSlotProps` | What the trigger shows: the selected label, or the placeholder. Not rendered with `searchIn="trigger"`, where the input shows the label. |
| `Tag` | widget | `AutocompleteTagSlotProps` | One selected value shown on the trigger in multiple mode. |
| `Clear` | widget | `AutocompleteClearSlotProps` | The control that empties the selection. Render a real button: it must be reachable by keyboard. |
| `Indicator` | widget | `AutocompleteIndicatorSlotProps` | The chevron, or a spinner while options are loading. |
| `Popup` | element | `AutocompletePopupSlotProps` | The floating surface holding the search box and the list. Replace it to use a library's own popover, in which case its positioning replaces the built-in one. |
| `Search` | element | `AutocompleteSearchSlotProps` | The search box. Plain input attributes, plus the ref that is focused when the popup opens. |
| `List` | element | `AutocompleteListSlotProps` | The `role="listbox"` element. It is also the scroll box that paging and the highlight measure against. |
| `Option` | element | `AutocompleteOptionSlotProps` | One `role="option"` row. Carries `data-highlighted`, `data-selected`, `data-disabled` and `data-index`. |
| `OptionLabel` | widget | `AutocompleteOptionLabelSlotProps` | The content of an option row. This is where rich rows belong: the `Option` part is an element part and only receives DOM props, so the option object is handed to this part instead. |
| `Check` | widget | `AutocompleteCheckSlotProps` | The mark on a selected option. |
| `Group` | element | `AutocompleteGroupSlotProps` | One group of options, rendered when `getOptionGroup` is set. The DOM props — `role="group"` and `aria-labelledby` pointing at the label — belong on the list that holds the group's rows. Inside a `<ul>` listbox that list needs a presentational `<li>` around it, since an `<li>` may not be a group itself: `<li role="none"><ul role="group" aria-labelledby="…">…</ul></li>`. `children` is already complete — the `GroupLabel` row, then the options — so `label` and `labelId` are there for a skin that wants them, not to be rendered again. |
| `GroupLabel` | widget | `AutocompleteGroupLabelSlotProps` | The heading row of a group. Put `id` on the rendered element, because the group is labelled by it, and keep `role="presentation"` so a list item is not announced as an item of the list. It is never highlighted and never focusable. |
| `Separator` | element | `AutocompleteSeparatorSlotProps` | The line between two sections of a grouped list. It is presentational and hidden from assistive technology, because a listbox may own only options and groups and the groups already mark the boundary. |
| `Empty` | widget | `AutocompleteEmptySlotProps` | Shown when the list has nothing to render. |
| `Loading` | widget | `AutocompleteLoadingSlotProps` | Shown while a first page is in flight and there is nothing to show yet. |
| `Error` | widget | `AutocompleteErrorSlotProps` | Shown when the last fetch failed. It replaces the list, so a failure never reads as "no results". |
| `Create` | widget | `AutocompleteCreateSlotProps` | The row offering to create whatever was searched for. Replace it to ask for more than a name — a colour, a category — without leaving the picker. |
| `LoadMore` | widget | `AutocompleteLoadMoreSlotProps` | The last row of a paged list. It is observed, so scrolling it into view requests the next page, and it is also a button, because an observer alone strands anyone not using a mouse. |

## Props

Every prop of `Autocomplete`, grouped as in the library's documentation. Controlled state comes in pairs: pass a value with its change handler, or only the default to leave the state inside the component.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `options` | `TOption[]` |  | The options to render. |
| `getOptionValue` | `GetOptionValue<TOption>` |  | Reads an option's id. Default: `option.value ?? option.id`. Every option's value must be unique: it keys the rows and builds their DOM ids, so two options with one value would share an id. |
| `getOptionLabel` | `GetOptionLabel<TOption>` |  | Reads an option's text. Default: `option.label ?? option.name`. |
| `optionDisabled` | `(option: TOption) => boolean` |  | Marks an option unselectable. It stays visible but cannot be landed on. |
| `getOptionGroup` | `GetOptionGroup<TOption>` |  | Reads the group an option belongs to, which is also the group's label. The options stay one flat list: groups appear in the order their first option does, an option joins its group wherever it sits (a later page included), and the options with no group form one unlabelled section where the first of them appeared. A group the search empties disappears with its label and separator. Read whenever the options or the search change. |
| `filter` | `OptionFilter<TOption>` |  | How to narrow options against the search. `false` filters nothing. |
| `selected` | `TOption \| null` |  | The option behind `value`, for an id whose option is not in `options`. |
| `selected` | `TOption[]` |  | The options behind `value`, for ids whose options are not in `options`. |
| `multiple` | `false` |  | One value at a time. |
| `multiple` | `true` |  | Several values, with the popup staying open on each pick. |
| `value` | `OptionValue \| null` |  | The selected id, or `null` for nothing. |
| `value` | `OptionValue[]` |  | The selected ids. |
| `defaultValue` | `OptionValue \| null` |  | The selected id while uncontrolled. |
| `defaultValue` | `OptionValue[]` |  | The selected ids while uncontrolled. |
| `onChange` | `(value: OptionValue \| null, option?: TOption) => void` |  | The new id and, where known, the option behind it. `null` means cleared. |
| `onChange` | `(values: OptionValue[], options: (TOption \| undefined)[]) => void` |  | The new ids, and the options index-aligned with them. An entry is `undefined` only when an id was supplied that no option has been seen for, so the two arrays always correspond. |
| `clearable` | `boolean` |  | Whether the clear control is offered. Default `true`. |
| `closeOnSelect` | `boolean` | `!multiple` | Whether picking closes the popup. Defaults to `!multiple`. |
| `name` | `string` |  | Renders a hidden input per value, so a plain HTML form submits it. |
| `searchable` | `boolean` |  | Whether the search box renders. `false` makes it a select with typeahead. |
| `searchIn` | `"popup" \| "trigger"` |  | Where the search box sits. `"popup"` puts it at the top of the popup, behind a trigger that is a button. `"trigger"` makes the trigger an editable combobox: an input inside the field, which keeps focus the whole time, shows the picked label while closed in single mode, and sits after the tags in multiple mode. Ignored when `searchable` is `false`, which keeps the button trigger. Default `"popup"`. |
| `searchQuery` | `string` |  | The search text. With `searchIn="trigger"` the parent owns what the input shows while this is not empty, open or closed; the picked label returns once the parent sets it back to `""`, which a close asks for through `onSearchChange("")`. |
| `defaultSearchQuery` | `string` |  | The search text while uncontrolled. |
| `onSearchChange` | `(query: string) => void` |  | Told when the search text changes, for remote options. |
| `minChars` | `number` |  | Don't search until this many characters are typed. |
| `open` | `boolean` |  | Whether the popup is open. |
| `defaultOpen` | `boolean` |  | Whether the popup starts open. |
| `onOpenChange` | `(open: boolean) => void` |  | Told when the popup opens or closes. |
| `loop` | `boolean` |  | Whether the highlight wraps past the ends. Default `false`. |
| `autoHighlight` | `boolean` |  | Highlight the first enabled option whenever the list opens or its contents change, so Enter picks the top match. A highlight the user moved themselves is kept until the search changes. Default `false`. |
| `disabled` | `boolean` |  | Nothing can be opened, picked or cleared. |
| `invalid` | `boolean` |  | The value failed validation. Sets `aria-invalid` on the combobox and `data-invalid` on the root and trigger, so a skin can show its error style. Pair it with `aria-errormessage` or `aria-describedby` pointing at the message. Default `false`. |
| `onBlur` | `(event: FocusEvent<HTMLElement>) => void` |  | Told when focus leaves the whole control, for form libraries. |
| `loading` | `boolean` |  | A first page is in flight. |
| `error` | `string` |  | The last fetch failed. Replaces the list. |
| `onRetry` | `() => void` |  | Offers a way back from the error. |
| `hasMore` | `boolean` |  | Another page exists. |
| `onLoadMore` | `() => void` |  | Asks for the next page. |
| `loadingMore` | `boolean` |  | That next page is in flight. |
| `creatable` | `boolean` |  | Offers a "create <query>" row when the search matches nothing. |
| `onCreate` | `(query: string) => void` |  | Creates the searched name. |
| `createLoading` | `boolean` |  | The create request is in flight. |
| `components` | `Partial<AutocompleteComponents<TOption>>` |  | Replace any part; the rest fall back to `SlotsmithProvider`'s `components.autocomplete`, then to the built-in ones. |
| `labels` | `Partial<AutocompleteLabels>` |  | Override any string. Wins over `locale`. |
| `locale` | `LocaleInput` | `the provider's` | The language: a locale object, or the tag of one given to `SlotsmithProvider`. Defaults to the provider's. |
| `slotProps` | `AutocompleteSlotProps` |  | Extra DOM props for the element parts. |
| `placeholder` | `string` | `labels.placeholder` | Trigger text when nothing is selected. Defaults to `labels.placeholder`. |
| `maxTags` | `number` | `2` | Tags shown before the rest collapse into `+N`. Defaults to 2. |
| `virtual` | `AutocompleteVirtualOptions` |  | Renders only the options in view, for lists of thousands: pass the virtualizer hook (`useVirtualizer` from `@tanstack/react-virtual`) as `use`, and the sizes. |
| `placement` | `PopupPlacement` | `bottom` | Which side of the trigger the popup prefers. Defaults to `bottom`. |
| `popupOffset` | `number` | `4` | Gap between trigger and popup, in pixels. Defaults to 4. |
| `matchTriggerWidth` | `boolean` | `true` | Make the popup exactly as wide as the trigger. Defaults to `true`. |
| `popupMaxHeight` | `number` | `320` | Upper bound on the popup's height, in pixels. Defaults to 320. |
| `portal` | `PortalTarget` | `false` | Renders the popup in a portal: `true` for `document.body`, or an element of your own. It then escapes any ancestor's `overflow`, `transform` or stacking context, and still counts as part of the control. Set the `--ss-*` tokens and the dark class on `:root` or on that element, which the popup then inherits from. Defaults to `false`. |

## Labels & locales

Every string `Autocomplete` shows or announces, with its English default. Pass `labels` to change some of them for one component, or `locale` to switch every string to another language. The demo below does both.

| Label | Type | Default | Description |
| --- | --- | --- | --- |
| `placeholder` | `string` | `"Select…"` | Trigger text when nothing is selected. |
| `search` | `string` | `"Search…"` | Placeholder for the search box. |
| `clear` | `string` | `"Clear selection"` | Accessible name for the clear control. |
| `toggle` | `string` | `"Show options"` | Accessible name for the button that opens and closes the list, with `searchIn="trigger"`. |
| `remove` | `(label: string) => string` | `(label) => `Remove ${label}`` | Accessible name for a tag's remove control. |
| `empty` | `string` | `"No results"` | Shown when a search returned nothing. |
| `loading` | `string` | `"Loading…"` | Shown while a first page is loading. |
| `minChars` | `(count: number) => string` | `(count) => `Type ${count} or more characters to search`` | Shown before `minChars` characters have been typed. |
| `retry` | `string` | `"Retry"` | Label for the retry control. |
| `create` | `(query: string) => string` | `(query) => `Create “${query}”`` | Label for the create row. |
| `creating` | `string` | `"Creating…"` | Label for the create row while the request is in flight. |
| `more` | `(count: number) => string` | `(count) => `+${count}`` | The overflow badge in multiple mode. |
| `loadMore` | `string` | `"Load more"` | Label for the load-more control. |
| `results` | `(count: number) => string` | `(count) => (count === 1 ? "1 result" : `${count} results`)` | Announced to screen readers when the result count changes. |

## Styling

The class names the fallbacks of `Autocomplete` put on their elements, and the tokens its stylesheet reads. Set a token on the component, or on any element around it, to restyle it; target a class only for what no token covers.

Classes: `sac`, `sac__body`, `sac__overflow`, `sac__sr-only`, `sac__chevron`, `sac__tick`, `sac__cross`, `sac__plus`, `sac__tag`, `sac__tag-label`, `sac__tag-remove`, `sac__clear`, `sac__spinner`, `sac__label`, `sac__message`, `sac__error`, `sac__button`, `sac__create`, `sac__create-button`, `sac__more`, `sac__more-button`, `sac__trigger`, `sac__trigger-input`, `sac__toggle`, `sac__value`, `sac__value--empty`, `sac__popup`, `sac__search`, `sac__list`, `sac__option`, `sac__group`, `sac__group-list`, `sac__group-label`, `sac__separator`

Tokens: `--sac-accent`, `--sac-border`, `--sac-danger`, `--sac-font-size`, `--sac-hover`, `--sac-muted`, `--sac-radius`, `--sac-surface`, `--sac-text`, `--ss-accent`, `--ss-border`, `--ss-danger`, `--ss-font-size`, `--ss-hover`, `--ss-muted`, `--ss-radius`, `--ss-surface`, `--ss-text`
