# Autocomplete (/docs/autocomplete)





`Autocomplete` is a text input with a suggestion dropdown. Unlike [`Select`](/docs/select), the **value is the string the user types** — `data` are just suggestions, and the user is free to type something that isn't in the list. It's built on the [`Combobox`](/docs/combobox) engine.

```tsx
import { Autocomplete } from '@42-central/ui-react/autocomplete';

<Autocomplete data={['Libft', 'ft_printf', 'Get Next Line']} placeholder="Type a project…" />
```

## Playground [#playground]

<story.Playground />

## Filtering [#filtering]

Suggestions filter as you type. Once the query **exactly matches** a suggestion (for example right after you pick one), the full list shows again instead of collapsing to that single match — so the dropdown stays useful when you reopen it.

`data` accepts the same shapes as [`Select`](/docs/select) — strings, `{ value, label?, disabled? }`, or grouped `{ group, items }`.

## Value is the text [#value-is-the-text]

`onChange` fires with the **input string** on every change — typing or picking a suggestion. There's no separate "selected option"; the value is the text.

```tsx
'use client';
import { useState } from 'react';
import { Autocomplete } from '@42-central/ui-react/autocomplete';

export function CityField() {
  const [value, setValue] = useState('');
  return (
    <Autocomplete
      data={['Paris', 'Lyon', 'Marseille', 'Lille']}
      value={value}
      onChange={setValue}
      placeholder="Your city"
      clearable
    />
  );
}
```

## Clearable [#clearable]

With `clearable`, a clear button appears once there's text.

<story.Preview variant="Clearable" />

## Start & end slots [#start--end-slots]

`startSlot` and `endSlot` place content at the field's inline-start / inline-end with the same slot layout as [`Input`](/docs/input). `endSlot` renders before the built-in clear button and chevron.

<Preview align="block" code="&#x22;import { Search } from 'lucide-react';\n\n<Autocomplete\n  data={['Paris', 'Lyon', 'Marseille']}\n  startSlot={<Search />}\n  placeholder=\&#x22;Your city\&#x22;\n/>&#x22;">
  <div className="w-full max-w-[20rem]">
    <Autocomplete data="['Paris', 'Lyon', 'Marseille', 'Lille']" startSlot="<Search />" placeholder="Your city" />
  </div>
</Preview>

## Custom rendering [#custom-rendering]

`renderItem` owns each suggestion's content — the same `(item, state) => ReactNode` renderer as [`Select`](/docs/select#custom-rendering), with `item` being `{ value, label, disabled }` and state `{ selected, highlighted }`. The field still shows the typed text; only the suggestion rows change. Define the function in a `"use client"` module so it survives the server → client boundary.

<story.Preview variant="Custom rendering" />

### Typed payload [#typed-payload]

Attach arbitrary data to a suggestion via `payload` and read it back as `item.payload` in `renderItem` — inferred from `data`, and `undefined` for bare-string or payload-less items.

```tsx
'use client';
import { Badge } from '@42-central/ui-react/badge';

const projects = [
  { value: 'Libft', payload: { xp: 420 } },
  { value: 'Webserv', payload: { xp: 1340 } },
];

// item.payload is inferred as { xp: number } | undefined
const renderSuggestion = (item) => (
  <span className="flex flex-1 items-center justify-between gap-2">
    {item.label}
    {item.payload && <Badge>{item.payload.xp} XP</Badge>}
  </span>
);

<Autocomplete data={projects} renderItem={renderSuggestion} />
```

## Label, description & error [#label-description--error]

Like the rest of the family, `label` / `description` / `error` / `required` are built in via [`Field`](/docs/field):

<Preview align="block" code="&#x22;<Autocomplete\n  label=\&#x22;Project\&#x22;\n  description=\&#x22;Pick one or type your own\&#x22;\n  data={['Libft', 'ft_printf', 'Get Next Line']}\n/>&#x22;">
  <div className="w-full max-w-[20rem]">
    <Autocomplete label="Project" description="Pick one or type your own" data="['Libft', 'ft_printf', 'Get Next Line', 'Minishell']" placeholder="Type a project…" />
  </div>
</Preview>

## API [#api]

<TypeTable
  type="{
  data: {
    type: 'string[] | SelectItem[] | SelectGroup[]',
    description: 'Suggestions. Same shapes as Select.',
  },
  value: { type: 'string', description: 'Controlled input text (the value).' },
  defaultValue: { type: 'string', description: 'Uncontrolled initial text.' },
  onChange: {
    type: '(value: string) => void',
    description: 'Fires with the input text on every change.',
  },
  renderItem: {
    type: '(item, state) => ReactNode',
    description: 'Render each suggestion yourself. item is { value, label, disabled }; state is { selected, highlighted }. Define it in a &#x22;use client&#x22; module.',
  },
  placeholder: { type: 'string', description: 'Placeholder shown while the field is empty.' },
  clearable: { type: 'boolean', default: 'false', description: 'Show a clear button once there is text.' },
  startSlot: { type: 'ReactNode', description: 'Inline-start adornment (icon, label).' },
  endSlot: { type: 'ReactNode', description: 'Inline-end adornment — before the clear button / chevron.' },
  empty: {
    type: 'ReactNode',
    default: '&#x22;Nothing found&#x22;',
    description: 'Message when no suggestion matches. Pass null to render nothing.',
  },
  size: { type: '&#x22;xs&#x22; | &#x22;sm&#x22; | &#x22;md&#x22; | &#x22;lg&#x22; | &#x22;xl&#x22;', default: '&#x22;md&#x22;', description: 'Control + option sizing.' },
  variant: { type: '&#x22;default&#x22; | &#x22;unstyled&#x22;', default: '&#x22;default&#x22;', description: 'Input shell treatment.' },
  color: { type: 'Color', default: '&#x22;brand&#x22;', description: 'Focus-ring accent.' },
  label: { type: 'ReactNode', description: 'Field label.' },
  description: { type: 'ReactNode', description: 'Field helper text.' },
  error: { type: 'ReactNode', description: 'Field error; sets the invalid state.' },
  required: { type: 'boolean', default: 'false', description: 'Required flag + asterisk.' },
  disabled: { type: 'boolean', default: 'false', description: 'Disable the control.' },
  readOnly: { type: 'boolean', default: 'false', description: 'Read-only input.' },
  name: { type: 'string', description: 'Name for the input (form submission).' },
  translations: {
    type: '{ triggerLabel?: string; clearTriggerLabel?: string }',
    description: &#x22;Accessible names of the chevron (default \&#x22;Show options\&#x22;) and the clear button (default \&#x22;Clear value\&#x22;) — the same keys across the combobox family (Select, Autocomplete, MultiSelect, ComboboxList, MultiComboboxList), so one translation object covers them all. A partial override keeps the other default.&#x22;,
  },
  classNames: { type: '{ field?, control?, input?, option?, popover? }', description: 'Per-part class overrides.' },
  testId: { type: 'string', description: 'data-testid on the real text input. Each data item also takes its own optional testId, defaulting to `item-${value}` — see the Testing guide.' },
}"
/>

## Accessibility [#accessibility]

* Full combobox keyboard model from Ark: type to filter, `↑` / `↓` to move, `Enter` to accept a suggestion, `Esc` to close.
* `role="combobox"` / `listbox` / `option` and `aria-activedescendant` / `aria-expanded` are handled by the Ark machine; label / description / error are associated via the built-in `Field`.
* Because typed text is always valid (`allowCustomValue`), the field never traps the user — they can submit whatever they enter.
* RTL is honored via the document `dir`.
