# Autocomplete

A text field that suggests values as people type; they may pick a suggestion or keep their own text.

- **Import:** `import { Autocomplete, AutocompleteClear, AutocompleteCollection, AutocompleteContent, AutocompleteEmpty, AutocompleteGroup, AutocompleteInput, AutocompleteItem, AutocompleteLabel, AutocompleteList, AutocompleteSeparator, AutocompleteStatus, AutocompleteTrigger } from "@booleanpress/ui/autocomplete"`
- **Also install:** `@base-ui/react`
- **APG Combobox (list autocomplete):** <https://www.w3.org/WAI/ARIA/apg/patterns/combobox/examples/combobox-autocomplete-list/>
- **Page:** <https://ui.booleanpress.com/components/autocomplete> · @booleanpress/ui 0.2.0

## Usage

`Autocomplete` is [Base UI's Autocomplete](https://base-ui.com/react/components/autocomplete) with the library's field and list look. It needs the `@base-ui/react` peer package. The value is the text in the field: a suggestion only fills it. When the value must be one of the options, use `Combobox`; for several free values, `TagsInput`.

```tsx
import { Label } from "@booleanpress/ui/label"
import {
  Autocomplete,
  AutocompleteContent,
  AutocompleteInput,
  AutocompleteItem,
  AutocompleteList,
} from "@booleanpress/ui/autocomplete"

const TAGS = ["billing", "bounce", "dkim", "refund"]

export function TagField() {
  return (
    <>
      <Label htmlFor="tag">Ticket tag</Label>
      <Autocomplete items={TAGS}>
        <AutocompleteInput id="tag" placeholder="Type a tag" />
        <AutocompleteContent>
          <AutocompleteList>
            {(tag: string) => (
              <AutocompleteItem key={tag} value={tag}>
                {tag}
              </AutocompleteItem>
            )}
          </AutocompleteList>
        </AutocompleteContent>
      </Autocomplete>
    </>
  )
}
```

It is uncontrolled with `defaultValue`, or controlled with `value` and `onValueChange`, which carry the field's text; `onValueChange`'s second argument has a `reason` (`"input-change"`, `"item-press"`…). When its form resets (a reset button, or React's reset after a form action), an uncontrolled field goes back to `defaultValue` and a controlled one is given the text it started with through `onValueChange`. Base UI filters `items` as people type; `filter={null}` shows them as given, for suggestions fetched from a server. `mode="both"` adds inline completion: the highlighted suggestion is written into the field as you move through the list.

`AutocompleteInput` is the field, as wide as its container, with `size` (`sm`, `default`, `lg`) and `variant="filled"` defaulting to the provider's `controlSize` and `fieldVariant`; `showClear`, `showTrigger` and `loading` add a clear button, a chevron cell and a spinner. The list hides while it has no suggestions, unless `AutocompleteEmpty` ("No results") or `AutocompleteStatus` has something to say. Inside a `Dialog`, `Sheet` or `Popover` the list works as anywhere else: a click picks and the overlay stays open, and Escape closes the list before the overlay.

## Examples

### Basic

Free text with suggestions that narrow as you type; text that matches none is kept.

```tsx
import { Label } from "@booleanpress/ui/label"
import {
  Autocomplete,
  AutocompleteContent,
  AutocompleteInput,
  AutocompleteItem,
  AutocompleteList,
} from "@booleanpress/ui/autocomplete"

const TAGS = ["billing", "bounce", "dkim", "dmarc", "dns", "feature request", "onboarding", "refund", "spf", "webhook"]

export default function AutocompleteBasic() {
  return (
    <div className="flex w-full max-w-56 flex-col gap-2">
      <Label htmlFor="autocomplete-tag">Ticket tag</Label>
      <Autocomplete items={TAGS}>
        <AutocompleteInput id="autocomplete-tag" placeholder="Type a tag" />
        <AutocompleteContent>
          <AutocompleteList>
            {(tag: string) => (
              <AutocompleteItem key={tag} value={tag}>
                {tag}
              </AutocompleteItem>
            )}
          </AutocompleteList>
        </AutocompleteContent>
      </Autocomplete>
    </div>
  )
}
```

### Inline completion

`mode="both"` writes the highlighted suggestion into the field while the arrow keys move through the list.

```tsx
import { Label } from "@booleanpress/ui/label"
import {
  Autocomplete,
  AutocompleteContent,
  AutocompleteInput,
  AutocompleteItem,
  AutocompleteList,
} from "@booleanpress/ui/autocomplete"

const ZONES = ["Africa/Cairo", "America/Chicago", "America/New_York", "Asia/Dhaka", "Asia/Tokyo", "Europe/Berlin", "Europe/London", "Europe/Madrid"]

export default function AutocompleteInline() {
  return (
    <div className="flex w-full max-w-56 flex-col gap-2">
      <Label htmlFor="autocomplete-zone">Time zone</Label>
      <Autocomplete items={ZONES} mode="both">
        <AutocompleteInput id="autocomplete-zone" placeholder="Europe/London" />
        <AutocompleteContent>
          <AutocompleteList>
            {(zone: string) => (
              <AutocompleteItem key={zone} value={zone}>
                {zone}
              </AutocompleteItem>
            )}
          </AutocompleteList>
        </AutocompleteContent>
      </Autocomplete>
    </div>
  )
}
```

### Async

Suggestions load from a stand-in help centre after 600 ms: `filter={null}`, a spinner and `AutocompleteStatus loading`; only the latest request fills the list, and a failed one says so.

```tsx
import { useRef, useState } from "react"
import { Label } from "@booleanpress/ui/label"
import {
  Autocomplete,
  AutocompleteContent,
  AutocompleteInput,
  AutocompleteItem,
  AutocompleteList,
  AutocompleteStatus,
} from "@booleanpress/ui/autocomplete"

const ARTICLES = [
  "Set up SPF for your domain",
  "Set up DKIM signing",
  "Read a bounce report",
  "Rotate an API key",
  "Route email per site",
  "Resend failed emails",
]

// Stands in for a request to the help centre: answers after 600 ms.
function searchArticles(query: string) {
  return new Promise<string[]>((resolve) =>
    setTimeout(() => resolve(ARTICLES.filter((title) => title.toLowerCase().includes(query.toLowerCase()))), 600)
  )
}

export default function AutocompleteAsync() {
  const [results, setResults] = useState<string[]>([])
  const [loading, setLoading] = useState(false)
  const [failed, setFailed] = useState(false)
  // Only the latest request may update the list: an earlier, slower answer is dropped.
  const latest = useRef(0)

  async function search(query: string) {
    const request = ++latest.current
    setFailed(false)
    if (!query.trim()) {
      setResults([])
      setLoading(false)
      return
    }
    setLoading(true)
    try {
      const found = await searchArticles(query)
      if (request === latest.current) setResults(found)
    } catch {
      if (request === latest.current) {
        setResults([])
        setFailed(true)
      }
    } finally {
      if (request === latest.current) setLoading(false)
    }
  }

  return (
    <div className="flex w-full max-w-64 flex-col gap-2">
      <Label htmlFor="autocomplete-help">Search the help centre</Label>
      <Autocomplete items={results} filter={null} onValueChange={(query, { reason }) => reason !== "item-press" && search(query)}>
        <AutocompleteInput id="autocomplete-help" placeholder="e.g. bounce" loading={loading} />
        <AutocompleteContent>
          <AutocompleteStatus loading={loading}>{failed ? "Could not search the help centre. Try again." : null}</AutocompleteStatus>
          <AutocompleteList>
            {(title: string) => (
              <AutocompleteItem key={title} value={title}>
                {title}
              </AutocompleteItem>
            )}
          </AutocompleteList>
        </AutocompleteContent>
      </Autocomplete>
    </div>
  )
}
```

### Groups

Grouped `items` with `AutocompleteGroup` and `AutocompleteLabel`, and `AutocompleteEmpty` when nothing matches.

```tsx
import { Label } from "@booleanpress/ui/label"
import {
  Autocomplete,
  AutocompleteContent,
  AutocompleteEmpty,
  AutocompleteGroup,
  AutocompleteInput,
  AutocompleteItem,
  AutocompleteLabel,
  AutocompleteList,
} from "@booleanpress/ui/autocomplete"

const GROUPS = [
  { value: "Recent", items: ["invoice overdue", "bounce rate"] },
  { value: "Customers", items: ["Northwind Traders", "Fabrikam Studio", "Contoso Clinics"] },
  { value: "Mailers", items: ["Amazon SES", "Postmark", "SendGrid"] },
]

export default function AutocompleteGroups() {
  return (
    <div className="flex w-full max-w-64 flex-col gap-2">
      <Label htmlFor="autocomplete-search">Search</Label>
      <Autocomplete items={GROUPS}>
        <AutocompleteInput id="autocomplete-search" placeholder="Search everything" />
        <AutocompleteContent>
          <AutocompleteEmpty />
          <AutocompleteList>
            {(group: (typeof GROUPS)[number]) => (
              <AutocompleteGroup key={group.value} items={group.items}>
                <AutocompleteLabel>{group.value}</AutocompleteLabel>
                {group.items.map((entry) => (
                  <AutocompleteItem key={entry} value={entry}>
                    {entry}
                  </AutocompleteItem>
                ))}
              </AutocompleteGroup>
            )}
          </AutocompleteList>
        </AutocompleteContent>
      </Autocomplete>
    </div>
  )
}
```

### Sizes

`sm` is 28 px high, `default` 35 px and `lg` 42 px.

```tsx
import {
  Autocomplete,
  AutocompleteContent,
  AutocompleteInput,
  AutocompleteItem,
  AutocompleteList,
} from "@booleanpress/ui/autocomplete"

const SIZES = [
  { size: "sm", label: "Small" },
  { size: "default", label: "Default" },
  { size: "lg", label: "Large" },
] as const

const SENDERS = ["Billing team", "Deliverability team", "Support team", "No reply"]

export default function AutocompleteSizes() {
  return (
    <div className="flex w-full max-w-56 flex-col gap-4">
      {SIZES.map(({ size, label }) => (
        <Autocomplete key={size} items={SENDERS}>
          <AutocompleteInput size={size} placeholder={label} aria-label={`Sender name, ${label.toLowerCase()} size`} />
          <AutocompleteContent>
            <AutocompleteList>
              {(sender: string) => (
                <AutocompleteItem key={sender} value={sender}>
                  {sender}
                </AutocompleteItem>
              )}
            </AutocompleteList>
          </AutocompleteContent>
        </Autocomplete>
      ))}
    </div>
  )
}
```

### Filled

`variant="filled"` fills the field with the grey `--field-filled`, on hover and focus too.

```tsx
import { Label } from "@booleanpress/ui/label"
import {
  Autocomplete,
  AutocompleteContent,
  AutocompleteInput,
  AutocompleteItem,
  AutocompleteList,
} from "@booleanpress/ui/autocomplete"

const SUBJECTS = ["Your receipt", "Your password reset link", "Welcome aboard", "Your invoice is ready"]

export default function AutocompleteFilled() {
  return (
    <div className="flex w-full max-w-64 flex-col gap-2">
      <Label htmlFor="autocomplete-subject">Subject</Label>
      <Autocomplete items={SUBJECTS}>
        <AutocompleteInput id="autocomplete-subject" variant="filled" placeholder="Write a subject" />
        <AutocompleteContent>
          <AutocompleteList>
            {(subject: string) => (
              <AutocompleteItem key={subject} value={subject}>
                {subject}
              </AutocompleteItem>
            )}
          </AutocompleteList>
        </AutocompleteContent>
      </Autocomplete>
    </div>
  )
}
```

### Disabled

`disabled` on the root and the input: the text stays, the field fills grey and suggests nothing.

```tsx
import { Label } from "@booleanpress/ui/label"
import {
  Autocomplete,
  AutocompleteContent,
  AutocompleteInput,
  AutocompleteItem,
  AutocompleteList,
} from "@booleanpress/ui/autocomplete"

const SENDERS = ["Billing team", "Support team"]

export default function AutocompleteDisabled() {
  return (
    <div className="flex w-full max-w-56 flex-col gap-2">
      <Label htmlFor="autocomplete-sender">Sender name</Label>
      <Autocomplete items={SENDERS} defaultValue="Support team" disabled>
        <AutocompleteInput id="autocomplete-sender" disabled />
        <AutocompleteContent>
          <AutocompleteList>
            {(sender: string) => (
              <AutocompleteItem key={sender} value={sender}>
                {sender}
              </AutocompleteItem>
            )}
          </AutocompleteList>
        </AutocompleteContent>
      </Autocomplete>
    </div>
  )
}
```

### Invalid

`aria-invalid` draws the error edge and placeholder; `aria-describedby` reads the message with the name.

```tsx
import { Label } from "@booleanpress/ui/label"
import {
  Autocomplete,
  AutocompleteContent,
  AutocompleteInput,
  AutocompleteItem,
  AutocompleteList,
} from "@booleanpress/ui/autocomplete"

const DOMAINS = ["example.com", "mail.example.com", "news.example.com"]

export default function AutocompleteInvalid() {
  return (
    <div className="flex w-full max-w-56 flex-col gap-2">
      <Label htmlFor="autocomplete-domain">Sending domain</Label>
      <Autocomplete items={DOMAINS} required>
        <AutocompleteInput
          id="autocomplete-domain"
          placeholder="example.com"
          aria-invalid
          aria-describedby="autocomplete-domain-error"
        />
        <AutocompleteContent>
          <AutocompleteList>
            {(domain: string) => (
              <AutocompleteItem key={domain} value={domain}>
                {domain}
              </AutocompleteItem>
            )}
          </AutocompleteList>
        </AutocompleteContent>
      </Autocomplete>
      <p id="autocomplete-domain-error" className="text-xs text-destructive-strong">
        Enter the domain you send from.
      </p>
    </div>
  )
}
```

## Accessibility

**Semantics.** The input has `role="combobox"`, `aria-autocomplete="list"` (`"both"` with inline completion), `aria-expanded` and `aria-controls` pointing at the `role="listbox"` of suggestions; the highlighted suggestion is its `aria-activedescendant`. Focus stays in the input.

**Labels.** Name the input with a `Label` whose `htmlFor` is its `id` (or a label round the field), `aria-label` or `aria-labelledby`. While the list is open, Base UI hides everything but the input and the list from assistive technology, the visible label included; so the input also carries its labels' text as its own `aria-label`, read when it mounts, as the list opens and when the label's text changes, and keeps its name. An input named by `aria-label` or `aria-labelledby` keeps that name. The clear button is named "Clear" and the chevron "Show options", from the provider.

**Focus.** The input is the only tab stop; the clear button and the chevron are pointer targets, since typing and the arrow keys do the same.

**Known limits.**

- Suggestions are not a selection: nothing is marked chosen in the list.

### Keyboard

| Key | Behaviour |
| --- | --- |
| ↓ | Opens the suggestions, then highlights the next one. |
| ↑ | Highlights the previous suggestion. |
| Enter | Fills the field with the highlighted suggestion and closes the list. |
| Escape | Closes the list and keeps the text; inside a dialog, the dialog stays open. |
| Home or End | Move the text cursor to the start or the end of the field. |
| A–Z | Typing narrows the suggestions; with `mode="both"` the highlighted one is written in. |

## API

### AutocompleteClear

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className` | `string` |  |  |
| `disabled` | `boolean` | `false` | Whether the component should ignore user interaction. |
| `keepMounted` | `boolean` | `false` | Whether the component should remain mounted in the DOM when not visible. |
| `nativeButton` | `boolean` | `true` | Whether the component renders a native `<button>` element when replacing it via the `render` prop. Set to `false` if the rendered element is not a button (for example, `<div>`). |
| `render` | `ReactElement<unknown, string \| JSXElementConstructor<any>> \| ComponentRenderFn<HTMLProps, ComboboxClearState>` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. Accepts a `ReactElement` or a function that returns the element to render. |
| `style` | `CSSProperties \| ((state: ComboboxClearState) => CSSProperties)` |  | Style applied to the element, or a function that returns a style object based on the component's state. |

### AutocompleteCollection

### AutocompleteContent

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `align` | `"center" \| "start" \| "end"` | `start` | How to align the popup relative to the specified side. |
| `alignOffset` | `number \| OffsetFunction` | `0` | Additional offset along the alignment axis in pixels. Also accepts a function that returns the offset to read the dimensions of the anchor and positioner elements, along with its side and alignment. The function takes a `data` object parameter with the following properties: - `data.anchor`: the dimensions of the anchor element with properties `width` and `height`. - `data.positioner`: the dimensions of the positioner element with properties `width` and `height`. - `data.side`: which side of the anchor element the positioner is aligned against. - `data.align`: how the positioner is aligned relative to the specified side. |
| `anchor` | `Element \| VirtualElement \| RefObject<Element \| null> \| (() => Element \| VirtualElement \| null) \| null` |  | An element to position the popup against. By default, the popup will be positioned against the trigger. |
| `className` | `string` |  |  |
| `container` | `HTMLElement \| ShadowRoot \| RefObject<HTMLElement \| ShadowRoot \| null> \| null` |  | A parent element to render the portal element into. |
| `finalFocus` | `boolean \| RefObject<HTMLElement \| null> \| ((closeType: InteractionType) => boolean \| void \| HTMLElement \| null)` |  | Determines the element to focus when the popup is closed. - `false`: Do not move focus. - `true`: Move focus based on the default behavior (trigger or previously focused element). - `RefObject`: Move focus to the ref element. - `function`: Called with the interaction type (`mouse`, `touch`, `pen`, or `keyboard`).   Return an element to focus, `true` to use the default behavior, or `false`/`undefined` to do nothing. |
| `initialFocus` | `boolean \| RefObject<HTMLElement \| null> \| ((openType: InteractionType) => boolean \| void \| HTMLElement \| null)` |  | Determines the element to focus when the popup is opened. - `false`: Do not move focus. - `true`: Move focus based on the default behavior (first tabbable element or popup). - `RefObject`: Move focus to the ref element. - `function`: Called with the interaction type (`mouse`, `touch`, `pen`, or `keyboard`).   Return an element to focus, `true` to use the default behavior, or `false`/`undefined` to do nothing. |
| `render` | `ReactElement<unknown, string \| JSXElementConstructor<any>> \| ComponentRenderFn<HTMLProps, ComboboxPopupState>` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. Accepts a `ReactElement` or a function that returns the element to render. |
| `side` | `"top" \| "bottom" \| "left" \| "right" \| "inline-end" \| "inline-start"` | `bottom` | Which side of the anchor element to align the popup against. May automatically change to avoid collisions. |
| `sideOffset` | `number \| OffsetFunction` | `2` | Distance between the anchor and the popup in pixels. Also accepts a function that returns the distance to read the dimensions of the anchor and positioner elements, along with its side and alignment. The function takes a `data` object parameter with the following properties: - `data.anchor`: the dimensions of the anchor element with properties `width` and `height`. - `data.positioner`: the dimensions of the positioner element with properties `width` and `height`. - `data.side`: which side of the anchor element the positioner is aligned against. - `data.align`: how the positioner is aligned relative to the specified side. |
| `style` | `CSSProperties \| ((state: ComboboxPopupState) => CSSProperties)` |  | Style applied to the element, or a function that returns a style object based on the component's state. |

### AutocompleteEmpty

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className` | `string` |  |  |
| `render` | `ReactElement<unknown, string \| JSXElementConstructor<any>> \| ComponentRenderFn<HTMLProps, ComboboxEmptyState>` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. Accepts a `ReactElement` or a function that returns the element to render. |
| `style` | `CSSProperties \| ((state: ComboboxEmptyState) => CSSProperties)` |  | Style applied to the element, or a function that returns a style object based on the component's state. |

### AutocompleteGroup

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className` | `string` |  |  |
| `items` | `readonly any[]` |  | Items to be rendered within this group. When provided, child `Collection` components will use these items. |
| `render` | `ReactElement<unknown, string \| JSXElementConstructor<any>> \| ComponentRenderFn<HTMLProps, ComboboxGroupState>` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. Accepts a `ReactElement` or a function that returns the element to render. |
| `style` | `CSSProperties \| ((state: ComboboxGroupState) => CSSProperties)` |  | Style applied to the element, or a function that returns a style object based on the component's state. |

### AutocompleteInput

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className` | `string` |  |  |
| `disabled` | `boolean` | `false` | Whether the component should ignore user interaction. |
| `loading` | `boolean` | `false` | Shows a spinner in the field while suggestions load. Pair it with `AutocompleteStatus`, which announces it. |
| `render` | `ReactElement<unknown, string \| JSXElementConstructor<any>> \| ComponentRenderFn<HTMLProps, ComboboxInputState>` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. Accepts a `ReactElement` or a function that returns the element to render. |
| `showClear` | `boolean` | `false` | Shows a clear button while the field has text; it empties the field and keeps focus in it. |
| `showTrigger` | `boolean` | `false` | Shows a chevron cell at the field's end that opens the whole list of suggestions. |
| `size` | `"default" \| "sm" \| "lg"` |  | 28, 35 or 42 px tall. Defaults to the provider's `controlSize`. |
| `style` | `CSSProperties \| ((state: ComboboxInputState) => CSSProperties)` |  | Style applied to the element, or a function that returns a style object based on the component's state. |
| `variant` | `"default" \| "filled"` |  | `filled` fills the field grey. Defaults to the provider's `fieldVariant`. |

### AutocompleteItem

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className` | `string` |  |  |
| `description` | `ReactNode` |  | A second, muted line under the label. |
| `disabled` | `boolean` | `false` | Whether the component should ignore user interaction. |
| `icon` | `ReactNode` |  | Leading media beside both lines, such as an icon or an avatar. |
| `index` | `number` |  | The index of the item in the list. Improves performance when specified by avoiding the need to calculate the index automatically from the DOM. |
| `nativeButton` | `boolean` | `true` | Whether the component renders a native `<button>` element when replacing it via the `render` prop. Set to `false` if the rendered element is not a button (for example, `<div>`). |
| `onClick` | `((event: BaseUIEvent<MouseEvent<HTMLDivElement, MouseEvent>>) => void)` |  | An optional click handler for the item when selected. It fires when clicking the item with the pointer, as well as when pressing `Enter` with the keyboard if the item is highlighted when the `Input` or `List` element has focus. |
| `render` | `ReactElement<unknown, string \| JSXElementConstructor<any>> \| ComponentRenderFn<HTMLProps, AutocompleteItemState>` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. Accepts a `ReactElement` or a function that returns the element to render. |
| `style` | `CSSProperties \| ((state: AutocompleteItemState) => CSSProperties)` |  | Style applied to the element, or a function that returns a style object based on the component's state. |
| `value` | `any` | `null` | A unique value that identifies this item. |

### AutocompleteLabel

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className` | `string` |  |  |
| `render` | `ReactElement<unknown, string \| JSXElementConstructor<any>> \| ComponentRenderFn<HTMLProps, ComboboxGroupLabelState>` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. Accepts a `ReactElement` or a function that returns the element to render. |
| `style` | `CSSProperties \| ((state: ComboboxGroupLabelState) => CSSProperties)` |  | Style applied to the element, or a function that returns a style object based on the component's state. |

### AutocompleteList

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className` | `string` |  |  |
| `render` | `ReactElement<unknown, string \| JSXElementConstructor<any>> \| ComponentRenderFn<HTMLProps, ComboboxListState>` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. Accepts a `ReactElement` or a function that returns the element to render. |
| `style` | `CSSProperties \| ((state: ComboboxListState) => CSSProperties)` |  | Style applied to the element, or a function that returns a style object based on the component's state. |

### AutocompleteSeparator

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className` | `string` |  |  |
| `orientation` | `"horizontal" \| "vertical"` | `'horizontal'` | The orientation of the separator. |
| `render` | `ReactElement<unknown, string \| JSXElementConstructor<any>> \| ComponentRenderFn<HTMLProps, AutocompleteSeparatorState>` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. Accepts a `ReactElement` or a function that returns the element to render. |
| `style` | `CSSProperties \| ((state: AutocompleteSeparatorState) => CSSProperties)` |  | Style applied to the element, or a function that returns a style object based on the component's state. |

### AutocompleteStatus

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className` | `string` |  |  |
| `loading` | `boolean` | `false` | Shows a spinner and the provider's `loadingResults` string in place of the children. |
| `render` | `ReactElement<unknown, string \| JSXElementConstructor<any>> \| ComponentRenderFn<HTMLProps, ComboboxStatusState>` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. Accepts a `ReactElement` or a function that returns the element to render. |
| `style` | `CSSProperties \| ((state: ComboboxStatusState) => CSSProperties)` |  | Style applied to the element, or a function that returns a style object based on the component's state. |

### AutocompleteTrigger

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className` | `string` |  |  |
| `disabled` | `boolean` | `false` | Whether the component should ignore user interaction. |
| `nativeButton` | `boolean` | `true` | Whether the component renders a native `<button>` element when replacing it via the `render` prop. Set to `false` if the rendered element is not a button (for example, `<div>`). |
| `render` | `ReactElement<unknown, string \| JSXElementConstructor<any>> \| ComponentRenderFn<HTMLProps, AutocompleteTriggerState>` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. Accepts a `ReactElement` or a function that returns the element to render. |
| `style` | `CSSProperties \| ((state: AutocompleteTriggerState) => CSSProperties)` |  | Style applied to the element, or a function that returns a style object based on the component's state. |

**Also exported:** `Autocomplete`, a helper the parts use; `useAutocompleteFilter`, a hook for the parts' shared state; call it inside the component's provider.

Every part takes `className`, merged with its defaults by `cn()`, and `ref`, which reaches the element it renders.

**Data attributes:** `data-slot="autocomplete-clear"` (AutocompleteClear), `data-slot="autocomplete-collection"` (AutocompleteCollection), `data-slot="autocomplete-content"` (AutocompleteContent), `data-slot="autocomplete-empty"` (AutocompleteEmpty), `data-slot="autocomplete-group"` (AutocompleteGroup), `data-slot="autocomplete-loading"` (AutocompleteInput), `data-slot="autocomplete-item"` (AutocompleteItem), `data-slot="autocomplete-label"` (AutocompleteLabel), `data-slot="autocomplete-list"` (AutocompleteList), `data-slot="autocomplete-separator"` (AutocompleteSeparator), `data-slot="autocomplete-status"` (AutocompleteStatus), `data-slot="autocomplete-trigger"` (AutocompleteTrigger), and `data-disabled`.

**Provider strings:** `toggleOptions`, `clear`, `noResults`, `loadingResults` (`BooleanUIProvider`'s `strings`).

## Theming

The field is `InputGroup`: `--field` (`--field-filled` filled), `--control` for the edge, `--ring` on focus, `--invalid` when invalid, icons in `--control-hover`. The list uses `--popover` and `--accent` for the highlighted suggestion; its motion is set once in `theme.css`.

| Token | Used for |
| --- | --- |
| `--accent` | background |
| `--accent-foreground` | text |
| `--border` | border, background |
| `--control-hover` | text |
| `--foreground` | text |
| `--muted-foreground` | text |
| `--popover` | background |
| `--popover-foreground` | text |
