# Autocomplete (/ui/components/react/autocomplete)



<Playground component="autocomplete" />

## Usage [#usage]

```tsx
import {
  Autocomplete,
  AutocompleteInput,
  AutocompleteContent,
  AutocompleteEmpty,
  AutocompleteList,
  AutocompleteItem,
} from '@appica/ui-react/autocomplete'
```

```tsx
const frameworks = ['Next.js', 'Remix', 'Astro', 'Nuxt.js', 'SvelteKit', 'SolidStart']

<Autocomplete items={frameworks}>
  <AutocompleteInput placeholder="Search a framework" />
  <AutocompleteContent>
    <AutocompleteEmpty>No frameworks found.</AutocompleteEmpty>
    <AutocompleteList>
      {(item) => (
        <AutocompleteItem key={item} value={item}>
          {item}
        </AutocompleteItem>
      )}
    </AutocompleteList>
  </AutocompleteContent>
</Autocomplete>
```

`Autocomplete` is a compound component. The root holds the data (`items`) and shared options (`size`, `variant`, `clearable`, `icon`); the parts compose the input and the filtered popup:

* **`AutocompleteInput`** — the text field, styled like [`Input`](/ui/components/react/input). Add a clear button with `clearable` and a dropdown chevron with `icon` (both on the root, both off by default).
* **`AutocompleteContent`** — the portalled, auto-positioned popup. Accepts the shared [floating props](/ui/components/react/popover) (`side`, `align`, `sideOffset`, …).
* **`AutocompleteList`** — the scrollable list. Pass a render function to map over the filtered items, or static `AutocompleteItem`s.
* **`AutocompleteItem`** — one option; its `value` is what selection reports.
* **`AutocompleteEmpty`** — shown only when the filter matches nothing.

## Examples [#examples]

### Variants [#variants]

`variant` switches the input appearance between a bordered `outline` (default) and a filled `soft` field.

```tsx
'use client'

import {
  Autocomplete,
  AutocompleteInput,
  AutocompleteContent,
  AutocompleteEmpty,
  AutocompleteList,
  AutocompleteItem,
} from '@appica/ui-react/autocomplete'

const frameworks = ['Next.js', 'Remix', 'Astro', 'Nuxt.js', 'SvelteKit', 'SolidStart', 'Gatsby', 'Vite']
const variants = ['outline', 'soft'] as const

export default function AutocompleteVariants() {
  return (
    <div className="flex w-full max-w-70 flex-col gap-4">
      {variants.map((variant) => (
        <div key={variant}>
          <Autocomplete items={frameworks} variant={variant}>
            <AutocompleteInput placeholder="Search a framework" aria-label={`Search (${variant})`} />
            <AutocompleteContent>
              <AutocompleteEmpty>No frameworks found.</AutocompleteEmpty>
              <AutocompleteList>
                {(item: string) => (
                  <AutocompleteItem key={item} value={item}>
                    {item}
                  </AutocompleteItem>
                )}
              </AutocompleteList>
            </AutocompleteContent>
          </Autocomplete>
        </div>
      ))}
    </div>
  )
}
```

### Sizes [#sizes]

`size` on the root scales the input, popup radius, and items together.

```tsx
'use client'

import {
  Autocomplete,
  AutocompleteInput,
  AutocompleteContent,
  AutocompleteEmpty,
  AutocompleteList,
  AutocompleteItem,
} from '@appica/ui-react/autocomplete'

const frameworks = ['Next.js', 'Remix', 'Astro', 'Nuxt.js', 'SvelteKit', 'SolidStart', 'Gatsby', 'Vite']
const sizes = ['sm', 'md', 'lg'] as const

export default function AutocompleteSizes() {
  return (
    <div className="flex w-full max-w-70 flex-col gap-4">
      {sizes.map((size) => (
        <div key={size}>
          <Autocomplete items={frameworks} size={size}>
            <AutocompleteInput placeholder="Search a framework" aria-label={`Search (${size})`} />
            <AutocompleteContent>
              <AutocompleteEmpty>No frameworks found.</AutocompleteEmpty>
              <AutocompleteList>
                {(item: string) => (
                  <AutocompleteItem key={item} value={item}>
                    {item}
                  </AutocompleteItem>
                )}
              </AutocompleteList>
            </AutocompleteContent>
          </Autocomplete>
        </div>
      ))}
    </div>
  )
}
```

### Clearable & chevron [#clearable--chevron]

Set `clearable` to add a clear (✕) button once there's a value, and `icon` to add a chevron that toggles the popup. Both render inside the input and are off by default.

```tsx
'use client'

import {
  Autocomplete,
  AutocompleteInput,
  AutocompleteContent,
  AutocompleteEmpty,
  AutocompleteList,
  AutocompleteItem,
} from '@appica/ui-react/autocomplete'

const frameworks = ['Next.js', 'Remix', 'Astro', 'Nuxt.js', 'SvelteKit', 'SolidStart', 'Gatsby', 'Vite']

export default function AutocompleteClearable() {
  return (
    <div className="w-full max-w-70">
      <Autocomplete items={frameworks} defaultValue="Next.js" clearable icon>
        <AutocompleteInput placeholder="Search a framework" aria-label="Search a framework" />
        <AutocompleteContent>
          <AutocompleteEmpty>No frameworks found.</AutocompleteEmpty>
          <AutocompleteList>
            {(item: string) => (
              <AutocompleteItem key={item} value={item}>
                {item}
              </AutocompleteItem>
            )}
          </AutocompleteList>
        </AutocompleteContent>
      </Autocomplete>
    </div>
  )
}
```

### Items with icons [#items-with-icons]

Items can render any content. Mark a leading [icon](/ui/icons) with `data-icon="start"` so it gets the same inner spacing as in [`Button`](/ui/components/react/button). When `items` are objects rather than strings, give the root an `itemToStringValue` so the filter and input know how to read each one. Here the selected command's icon is mirrored back into the input via `AutocompleteInput`'s `startSlot`.

```tsx
'use client'

import * as React from 'react'
import {
  Autocomplete,
  AutocompleteInput,
  AutocompleteContent,
  AutocompleteEmpty,
  AutocompleteList,
  AutocompleteItem,
} from '@appica/ui-react/autocomplete'
import { LayoutDashboard, User, Settings, CreditCard, Bell, Mail } from '@appica/icons-react'

interface Command {
  label: string
  icon: React.ElementType
}

const commands: Command[] = [
  { label: 'Dashboard', icon: LayoutDashboard },
  { label: 'Profile', icon: User },
  { label: 'Settings', icon: Settings },
  { label: 'Billing', icon: CreditCard },
  { label: 'Notifications', icon: Bell },
  { label: 'Messages', icon: Mail },
]

export default function AutocompleteWithIcons() {
  const [value, setValue] = React.useState('')
  const selected = commands.find((command) => command.label === value)

  return (
    <div className="w-full max-w-70">
      <Autocomplete
        items={commands}
        value={value}
        onValueChange={(v: string) => setValue(v)}
        itemToStringValue={(item) => (item as Command).label}
        clearable
        icon
      >
        <AutocompleteInput
          placeholder="Search commands"
          aria-label="Search commands"
          startSlot={selected ? <selected.icon aria-hidden /> : null}
        />
        <AutocompleteContent>
          <AutocompleteEmpty>No commands found.</AutocompleteEmpty>
          <AutocompleteList>
            {(command: Command) => (
              <AutocompleteItem key={command.label} value={command}>
                <command.icon data-icon="start" />
                {command.label}
              </AutocompleteItem>
            )}
          </AutocompleteList>
        </AutocompleteContent>
      </Autocomplete>
    </div>
  )
}
```

### Country search [#country-search]

The same object-item pattern with [country flags](/ui/country-flags) — a typical country picker. Each item renders a flag before the name, and the selected country's flag is mirrored into the input through `startSlot`.

```tsx
'use client'

import * as React from 'react'
import {
  Autocomplete,
  AutocompleteInput,
  AutocompleteContent,
  AutocompleteEmpty,
  AutocompleteList,
  AutocompleteItem,
} from '@appica/ui-react/autocomplete'
import { CountryFlagRounded } from '@appica/country-flags-react'

interface Country {
  code: string
  name: string
}

const countries: Country[] = [
  { code: 'au', name: 'Australia' },
  { code: 'br', name: 'Brazil' },
  { code: 'ca', name: 'Canada' },
  { code: 'fr', name: 'France' },
  { code: 'de', name: 'Germany' },
  { code: 'it', name: 'Italy' },
  { code: 'jp', name: 'Japan' },
  { code: 'es', name: 'Spain' },
  { code: 'gb', name: 'United Kingdom' },
  { code: 'us', name: 'United States' },
]

export default function AutocompleteCountrySearch() {
  const [value, setValue] = React.useState('')
  const selected = countries.find((country) => country.name === value)

  return (
    <div className="w-full max-w-70">
      <Autocomplete
        items={countries}
        value={value}
        onValueChange={(v: string) => setValue(v)}
        itemToStringValue={(item) => (item as Country).name}
        clearable
        icon
      >
        <AutocompleteInput
          placeholder="Search a country"
          aria-label="Search a country"
          startSlot={selected ? <CountryFlagRounded code={selected.code} aria-hidden /> : null}
        />
        <AutocompleteContent>
          <AutocompleteEmpty>No countries found.</AutocompleteEmpty>
          <AutocompleteList>
            {(country: Country) => (
              <AutocompleteItem key={country.code} value={country}>
                <CountryFlagRounded code={country.code} data-icon="start" aria-hidden />
                {country.name}
              </AutocompleteItem>
            )}
          </AutocompleteList>
        </AutocompleteContent>
      </Autocomplete>
    </div>
  )
}
```

### Grouped options [#grouped-options]

Pass a grouped `items` array — `{ value, items }[]` — and render each group with `AutocompleteGroup` + `AutocompleteLabel` + `AutocompleteCollection`. The outer render function iterates groups; `AutocompleteCollection` renders the items inside each.

```tsx
'use client'

import {
  Autocomplete,
  AutocompleteInput,
  AutocompleteContent,
  AutocompleteEmpty,
  AutocompleteList,
  AutocompleteItem,
  AutocompleteGroup,
  AutocompleteLabel,
  AutocompleteCollection,
} from '@appica/ui-react/autocomplete'

interface ProduceGroup {
  value: string
  items: string[]
}

const groups: ProduceGroup[] = [
  { value: 'Fruits', items: ['Apple', 'Banana', 'Cherry', 'Grapefruit', 'Mango', 'Orange', 'Strawberry'] },
  { value: 'Vegetables', items: ['Broccoli', 'Carrot', 'Cucumber', 'Lettuce', 'Potato', 'Spinach', 'Tomato'] },
  { value: 'Herbs', items: ['Basil', 'Mint', 'Oregano', 'Parsley', 'Rosemary', 'Thyme'] },
]

export default function AutocompleteGrouped() {
  return (
    <div className="w-full max-w-70">
      <Autocomplete items={groups}>
        <AutocompleteInput placeholder="Search produce" aria-label="Search produce" />
        <AutocompleteContent>
          <AutocompleteEmpty>No produce found.</AutocompleteEmpty>
          <AutocompleteList>
            {(group: ProduceGroup) => (
              <AutocompleteGroup key={group.value} items={group.items}>
                <AutocompleteLabel>{group.value}</AutocompleteLabel>
                <AutocompleteCollection>
                  {(item: string) => (
                    <AutocompleteItem key={item} value={item}>
                      {item}
                    </AutocompleteItem>
                  )}
                </AutocompleteCollection>
              </AutocompleteGroup>
            )}
          </AutocompleteList>
        </AutocompleteContent>
      </Autocomplete>
    </div>
  )
}
```

### Grid layout [#grid-layout]

Set `grid` on the root to lay options out in a grid (arrow keys move in two dimensions). `AutocompleteList`'s `cols` controls how many items per row.

```tsx
'use client'

import {
  Autocomplete,
  AutocompleteInput,
  AutocompleteContent,
  AutocompleteEmpty,
  AutocompleteList,
  AutocompleteItem,
} from '@appica/ui-react/autocomplete'

const tags = ['React', 'Vue', 'Svelte', 'Angular', 'Solid', 'Qwik', 'Preact', 'Lit']

export default function AutocompleteGrid() {
  return (
    <div className="w-full max-w-70">
      <Autocomplete items={tags} grid icon>
        <AutocompleteInput placeholder="Search a library" aria-label="Search a library" />
        <AutocompleteContent>
          <AutocompleteEmpty>No libraries found.</AutocompleteEmpty>
          <AutocompleteList cols={2}>
            {(item: string) => (
              <AutocompleteItem key={item} value={item} className="flex-1">
                {item}
              </AutocompleteItem>
            )}
          </AutocompleteList>
        </AutocompleteContent>
      </Autocomplete>
    </div>
  )
}
```

### Controlled [#controlled]

Drive the input value yourself with `value` + `onValueChange` — useful for syncing with form state or reacting to every keystroke.

```tsx
'use client'

import * as React from 'react'
import {
  Autocomplete,
  AutocompleteInput,
  AutocompleteContent,
  AutocompleteEmpty,
  AutocompleteList,
  AutocompleteItem,
} from '@appica/ui-react/autocomplete'

const frameworks = ['Next.js', 'Remix', 'Astro', 'Nuxt.js', 'SvelteKit', 'SolidStart', 'Gatsby', 'Vite']

export default function AutocompleteControlled() {
  const [value, setValue] = React.useState('')

  return (
    <div className="flex w-full max-w-70 flex-col gap-3">
      <Autocomplete items={frameworks} value={value} onValueChange={(v: string) => setValue(v)} clearable>
        <AutocompleteInput placeholder="Search a framework" aria-label="Search a framework" />
        <AutocompleteContent>
          <AutocompleteEmpty>No frameworks found.</AutocompleteEmpty>
          <AutocompleteList>
            {(item: string) => (
              <AutocompleteItem key={item} value={item}>
                {item}
              </AutocompleteItem>
            )}
          </AutocompleteList>
        </AutocompleteContent>
      </Autocomplete>
      <p className="text-foreground-muted text-sm">
        Current value: <span className="text-foreground font-medium">{value || '—'}</span>
      </p>
    </div>
  )
}
```

## RTL [#rtl]

Every Appica UI component supports right-to-left layouts out of the box. Set the `dir` attribute on a container (commonly your `<html>` element) so CSS logical properties resolve correctly, and wrap your tree in `DirectionProvider` so direction-aware behavior — roving focus, popup placement, and the like — follows the same direction.

```tsx
import { DirectionProvider } from '@appica/ui-react/providers/direction-provider'

export default function RootLayout({ children }) {
  return (
    <html lang="ar" dir="rtl">
      <body>
        <DirectionProvider dir="rtl">{children}</DirectionProvider>
      </body>
    </html>
  )
}
```

For setup details and caveats, see the [RTL guide](/ui/docs/react/rtl).

<RtlPreview component="autocomplete" />

## API reference [#api-reference]

`Autocomplete` wraps [Base UI's Autocomplete](https://base-ui.com/react/components/autocomplete). Each part forwards every remaining prop to the matching Base UI primitive, so anything it accepts works here — the tables below list the Appica additions and the most common props. See the Base UI docs for the complete API.

### Autocomplete [#autocomplete]

The root. Holds the data and the shared styling/behavior options; renders no DOM of its own.

| Prop                | <ColMinWidth width="180">Type</ColMinWidth>          | Default     | <ColMinWidth width="220">Description</ColMinWidth>                                   |
| ------------------- | ---------------------------------------------------- | ----------- | ------------------------------------------------------------------------------------ |
| `items`             | <Code>readonly T\[] \| {'{ value, items }[]'}</Code> | —           | The data to filter. A flat array, or `{ value, items }` objects for grouped options. |
| `size`              | <Code>'sm' \| 'md' \| 'lg'</Code>                    | `'md'`      | Input height, popup radius, and item sizing.                                         |
| `variant`           | <Code>'outline' \| 'soft'</Code>                     | `'outline'` | Input appearance — bordered or filled.                                               |
| `clearable`         | `boolean`                                            | `false`     | Render a clear button inside the input when a value is present.                      |
| `icon`              | `boolean`                                            | `false`     | Render a chevron button that toggles the popup.                                      |
| `grid`              | `boolean`                                            | `false`     | Lay options out as a navigable grid instead of a list.                               |
| `value`             | `string`                                             | —           | Controlled input value. Pair with `onValueChange`.                                   |
| `defaultValue`      | `string`                                             | —           | Uncontrolled initial input value.                                                    |
| `onValueChange`     | <Code>(value, details) => void</Code>                | —           | Fires as the value changes (typing, selection, clear).                               |
| `itemToStringValue` | <Code>(item: T) => string</Code>                     | —           | Required for object items: how the filter and input read each one.                   |
| `mode`              | <Code>'list' \| 'inline' \| 'both' \| 'none'</Code>  | `'list'`    | Inline modes autofill the input with the highlighted item while navigating.          |

### AutocompleteInput [#autocompleteinput]

The text field. Renders Base UI's `Input` inside an `InputGroup`, styled like [`Input`](/ui/components/react/input).

| Prop          | Type        | Default | Description                                                    |
| ------------- | ----------- | ------- | -------------------------------------------------------------- |
| `startSlot`   | `ReactNode` | —       | Adornment rendered before the field, inside the input frame.   |
| `endSlot`     | `ReactNode` | —       | Adornment rendered after the field, inside the input frame.    |
| `placeholder` | `string`    | `' '`   | Placeholder text.                                              |
| `disabled`    | `boolean`   | `false` | Disables the field and the clear/chevron buttons.              |
| `className`   | `string`    | —       | Extra classes on the input group, merged via `tailwind-merge`. |

Also forwards every native `<input>` prop (`onChange`, `name`, `aria-*`, `ref`, …).

### AutocompleteContent [#autocompletecontent]

The portalled, auto-positioned popup wrapping the list. Accepts the shared floating props.

| Prop              | <ColMinWidth width="160">Type</ColMinWidth>         | Default    | Description                                                     |
| ----------------- | --------------------------------------------------- | ---------- | --------------------------------------------------------------- |
| `side`            | <Code>'top' \| 'right' \| 'bottom' \| 'left'</Code> | `'bottom'` | Preferred side of the input to open on.                         |
| `align`           | <Code>'start' \| 'center' \| 'end'</Code>           | —          | Alignment along that side.                                      |
| `sideOffset`      | `number`                                            | `6`        | Gap between the input and the popup.                            |
| `alignOffset`     | `number`                                            | —          | Shift along the alignment axis.                                 |
| `container`       | <Code>HTMLElement \| ref \| null</Code>             | —          | Portal target — render the popup into a different node.         |
| `keepMounted`     | `boolean`                                           | `false`    | Keep the popup mounted in the DOM while closed.                 |
| `positionerProps` | `object`                                            | —          | Escape hatch for the positioner element (`style`, `render`, …). |
| `portalProps`     | `object`                                            | —          | Escape hatch for the portal element (`style`, `render`, …).     |
| `className`       | `string`                                            | —          | Extra classes on the popup.                                     |

### AutocompleteList [#autocompletelist]

The scrollable list container.

| Prop       | <ColMinWidth width="220">Type</ColMinWidth>          | Default | Description                                                               |
| ---------- | ---------------------------------------------------- | ------- | ------------------------------------------------------------------------- |
| `cols`     | `number`                                             | —       | Items per row in grid mode (defaults to 2 when `grid` is set).            |
| `children` | <Code>ReactNode \| (item, index) => ReactNode</Code> | —       | A render function over the filtered items, or static `AutocompleteItem`s. |

### AutocompleteItem [#autocompleteitem]

One option in the list.

| Prop        | Type     | Default | Description                                             |
| ----------- | -------- | ------- | ------------------------------------------------------- |
| `value`     | `T`      | —       | The item this option represents; reported on selection. |
| `className` | `string` | —       | Extra classes (e.g. `flex-1` to fill a grid cell).      |

### AutocompleteEmpty [#autocompleteempty]

Shown only when the filter matches nothing — otherwise it renders nothing.

| Prop        | Type        | Default | Description                                 |
| ----------- | ----------- | ------- | ------------------------------------------- |
| `children`  | `ReactNode` | —       | The empty-state message.                    |
| `className` | `string`    | —       | Extra classes, merged via `tailwind-merge`. |

### AutocompleteStatus [#autocompletestatus]

A persistent row that stays mounted (e.g. a loading or count message), unlike `AutocompleteEmpty`.

| Prop        | Type        | Default | Description                                 |
| ----------- | ----------- | ------- | ------------------------------------------- |
| `children`  | `ReactNode` | —       | The status message.                         |
| `className` | `string`    | —       | Extra classes, merged via `tailwind-merge`. |

### AutocompleteGroup [#autocompletegroup]

Wraps one labelled section of a grouped list.

| Prop        | Type           | Default | Description                                                            |
| ----------- | -------------- | ------- | ---------------------------------------------------------------------- |
| `items`     | `readonly T[]` | —       | The group's items, so the inner `AutocompleteCollection` can map them. |
| `className` | `string`       | —       | Extra classes, merged via `tailwind-merge`.                            |

### AutocompleteLabel [#autocompletelabel]

The visible label for an `AutocompleteGroup`.

| Prop        | Type        | Default | Description                                 |
| ----------- | ----------- | ------- | ------------------------------------------- |
| `children`  | `ReactNode` | —       | The label text.                             |
| `className` | `string`    | —       | Extra classes, merged via `tailwind-merge`. |

### AutocompleteCollection [#autocompletecollection]

Renders the items inside an `AutocompleteGroup`.

| Prop       | <ColMinWidth width="200">Type</ColMinWidth> | Default | Description                                                         |
| ---------- | ------------------------------------------- | ------- | ------------------------------------------------------------------- |
| `children` | <Code>(item, index) => ReactNode</Code>     | —       | Render function for each item — `(item) => <AutocompleteItem … />`. |

### AutocompleteSeparator [#autocompleteseparator]

A thin divider between sections.

| Prop        | Type     | Default | Description                                 |
| ----------- | -------- | ------- | ------------------------------------------- |
| `className` | `string` | —       | Extra classes, merged via `tailwind-merge`. |

### AutocompleteTrigger [#autocompletetrigger]

A standalone button that opens the popup (e.g. a tag picker that isn't a text field).

| Prop        | <ColMinWidth width="200">Type</ColMinWidth>                   | Default | Description                                 |
| ----------- | ------------------------------------------------------------- | ------- | ------------------------------------------- |
| `render`    | <Code>ReactElement \| ((props, state) => ReactElement)</Code> | —       | Compose the trigger with another element.   |
| `className` | `string`                                                      | —       | Extra classes, merged via `tailwind-merge`. |

Also forwards every native `<button>` prop (`onClick`, `aria-*`, `ref`, …).

### AutocompleteValue [#autocompletevalue]

Renders the current value for read-only display.

| Prop       | <ColMinWidth width="200">Type</ColMinWidth>    | Default | Description                                        |
| ---------- | ---------------------------------------------- | ------- | -------------------------------------------------- |
| `children` | <Code>ReactNode \| (value) => ReactNode</Code> | —       | Static content, or a render function of the value. |

## Accessibility [#accessibility]

* The input has `role="combobox"`; the popup is a `listbox` (or `grid` when `grid` is set) and items are `option`/`gridcell`. Selection and active state are wired through `aria-activedescendant`.
* **↑/↓** move the highlight, **Enter** selects, **Escape** closes the popup, and typing filters the list.
* The clear and chevron buttons have `aria-label`s (`Clear selection`, `Toggle popup`) and become `disabled` when the input is disabled.
* Always give `AutocompleteInput` a visible label or an `aria-label`.
* Popup enter/exit transitions are wrapped so they're skipped when the user prefers reduced motion.
