<!-- Generated by scripts/gen-docs.mts from the JSDoc in registry/. Do not edit; run `pnpm run docs`. -->

# Combobox

A text input that filters a list of items as people type, on the Field contract: pass `label`, `items`, and `error` from your server action or client check. It picks one item, several as chips with `multiple`, or accepts any text with `allowCustomValue`. Pass `onInputChange` and `loading` to search on your server. A clear button shows while there is a value. On a phone (below 640px with touch) the field is a button that opens a full-height bottom sheet with the search input pinned at the top, and a clear button sits beside it. With `allowCustomValue` the sheet's first row keeps what was typed (`customValueLabel`). The sheet opens with focus on itself, not the search input, so the keyboard does not cover the list until people tap the search (as Base UI does for touch).

```tsx
import { Combobox } from "@/components/ouro/combobox/combobox";
```

## Example

```tsx
<Combobox label="Herb" name="herb" items={["Basil", "Dill", "Thyme"]} />
<Combobox label="Tags" name="tags" multiple items={tags} defaultValue={["Quick"]} />
<Combobox label="Recipe" items={results} loading={searching} onInputChange={search} />
<Combobox label="Cuisine" name="cuisine" allowCustomValue items={["Italian", "Thai"]} />
```

## Use instead when

- Use `Select` instead for a short list people don't need to search, and `TextField` for free text with no suggestions.

## Props

The props the modes share. Type: `ComboboxProps`.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className` | `string` |  | Layout only: margins, width, grid placement (`mt-4`, `col-span-2`). Appearance classes warn in development; style fields in your theme. |
| `clearLabel` | `string` | `"Clear"` | The name of the button that clears the field, as visually hidden text and not an `aria-label`, so a test's label lookup for the field finds the field alone. It shows while there is something to clear. Translate it with your app. |
| `description` | `ReactNode` |  | Supporting text under the label, read after the label through `aria-describedby`. |
| `disabled` | `boolean` |  | Disables the field and its buttons, and fades it. |
| `doneLabel` | `string` | `"Done"` | The label of the button that closes the sheet on a phone (a visible button with `multiple`). Translate it with your app. |
| `emptyLabel` | `string` | `"No results"` | The text the list shows when nothing matches. Translate it with your app. |
| `error` | `ReadonlyErrorMessages` |  | What is wrong, from your server action or client check. The message (or several, as a list) shows under the field, which gets `aria-invalid`. A message is text or a React node (one with a link); an empty string or array means valid. |
| `hideLabel` | `boolean` |  | Hides the label visually; screen readers still read it as the field's name. |
| `items` (required) | `readonly ComboboxItem[]` |  | What people pick from: strings, or `{ value, label }` objects. Combobox filters them as people type, unless you pass `onInputChange`, which makes them your search results. |
| `label` (required) | `string` |  | What the field is for, shown above it and read as its name. Required: every control needs a name. To hide it visually, keep it and add `hideLabel`. A placeholder is not a label. |
| `loading` | `boolean` |  | A search is running: the list shows a spinner and `loadingLabel`, and is `aria-busy`. |
| `loadingLabel` | `string` | `"Loading…"` | The text the list shows while `loading`. Translate it with your app. |
| `name` | `string` |  | The form field name the picked value submits under; `multiple` submits one entry per pick. |
| `onInputChange` | `((text: string) => void)` |  | Called with the text as people type. Pass it to search on your server: Combobox then shows `items` as you give them, without filtering, and you set `loading` while a search runs. Keep the picked items in `items`, so their labels stay known. |
| `placeholder` | `string` |  | An example shown while the input is empty: `"Search herbs"`. It is not a label; pass `label`. |
| `ref` | `Ref<HTMLInputElement>` |  | The input element. On a phone, it is the search input in the sheet, while the sheet is open. |
| `required` | `boolean` |  | Something must be picked: the input gets `aria-required`, and the label a marker. |

## Modes

`ComboboxProps` is a union: pass the props of exactly one mode. Mixing them is a type error.

### Custom: `ComboboxCustomProps`

A Combobox that accepts any text, suggesting matching items as people type.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `allowCustomValue` (required) | `true` |  | Accept any text: the value is what is in the input, and items are suggestions. |
| `customValueLabel` | `((text: string) => string)` | (text) => `Use “${text}”` | The sheet's row (on a phone) that keeps what was typed, from the text. Translate it with your app. |
| `defaultValue` | `string` |  | The text at first, when the field keeps its own. |
| `multiple` | `false` |  | Not with `allowCustomValue`: it keeps one typed value. |
| `onValueChange` | `((value: string) => void)` |  | Called with the text as people type or pick a suggestion. A suggestion puts its `label` in the input, and the text is the value, so an `{ value, label }` item submits its label. |
| `value` | `string` |  | The text, when you control it. |

Not in this mode (typed `never`):

- `removeLabel`: Only with `multiple`.

### Multiple: `ComboboxMultipleProps`

A Combobox that picks several items, shown as chips in the input.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `allowCustomValue` | `false` |  | Not with `multiple`: `allowCustomValue` keeps one typed value. |
| `defaultValue` | `string[]` |  | The items picked at first, when the field keeps its own. |
| `multiple` (required) | `true` |  | Pick several items, shown as chips; the value is an array. |
| `onValueChange` | `((value: string[]) => void)` |  | Called with the picked items' values. |
| `removeLabel` | `((label: string) => string)` | (label) => `Remove ${label}` | The name of a chip's remove button, from the item's label, as visually hidden text and not an `aria-label`. Translate it with your app. |
| `value` | `string[]` |  | The picked items' values, when you control them. |

Not in this mode (typed `never`):

- `customValueLabel`: Only with `allowCustomValue`.

### Single: `ComboboxSingleProps`

A Combobox that picks one item: its value, or `null` when nothing is picked.

| Prop | Type | Description |
| --- | --- | --- |
| `allowCustomValue` | `false` | Accept any text, not only an item. |
| `defaultValue` | `string \| null` | The item picked at first, when the field keeps its own. |
| `multiple` | `false` | Pick several items, shown as chips. |
| `onValueChange` | `((value: string \| null) => void)` | Called with the picked item's value, or `null` when cleared. |
| `value` | `string \| null` | The picked item's value, when you control it; `null` is nothing picked. |

Not in this mode (typed `never`):

- `customValueLabel`: Only with `allowCustomValue`.
- `removeLabel`: Only with `multiple`.

## Don't use

These are not props. Passing one fails the type check with the fix in the error, and in development a console message names the fix.

- `creatable`: Not a prop. Ouro UI uses `allowCustomValue`: `<Combobox allowCustomValue>`.
- `errorMessage`: Not a prop. Ouro UI uses `error`: `<Combobox error="Required.">`.
- `freeSolo`: Not a prop. Ouro UI uses `allowCustomValue`: `<Combobox allowCustomValue>`.
- `helperText`: Not a prop. Ouro UI uses `description`: `<Combobox description="...">`.
- `invalid`: Not a prop. `error` marks the field invalid: `<Combobox error="What is wrong">`.
- `isClearable`: Not a prop. The clear button shows whenever there is something to clear.
- `isDisabled`: Not a prop. Ouro UI uses `disabled`: `<Combobox disabled>`.
- `isInvalid`: Not a prop. `error` marks the field invalid: `<Combobox error="What is wrong">`.
- `isLoading`: Not a prop. Ouro UI uses `loading`: `<Combobox loading>`.
- `isMulti`: Not a prop. Ouro UI uses `multiple`: `<Combobox multiple>`.
- `isRequired`: Not a prop. Ouro UI uses `required`: `<Combobox required>`.
- `noOptionsMessage`: Not a prop. Ouro UI uses `emptyLabel`: `<Combobox emptyLabel="No herbs">`.
- `onChange`: Not a prop. Combobox reports the pick itself: `<Combobox onValueChange={(value) => …}>`.
- `onInputValueChange`: Not a prop. Ouro UI uses `onInputChange`: `<Combobox onInputChange={(text) => …}>`.
- `onSearch`: Not a prop. Ouro UI uses `onInputChange`: `<Combobox onInputChange={(text) => …}>`.
- `options`: Not a prop. Ouro UI uses `items`: `<Combobox items={["Basil", "Dill"]}>`.

## `className` is for layout only

Pass margins, width and grid placement (`mt-4`, `w-full`, `col-span-2`). Your classes always win over Ouro UI's: Combobox merges `className` last with `cn` (tailwind-merge), so a class that conflicts with its own replaces it.[^layout-classes]

Appearance comes from props, not classes. In development, a class that sets appearance logs one warning per component instance, naming the prop to use instead. A class sets appearance when, after its variants (`hover:`, `sm:`) and any `!` are removed, it starts with `bg-`, `text-`, `border-`, `rounded-`, `shadow-`, `font-`, `ring-`, `from-`, `via-`, `to-`, `fill-` or `stroke-`, or is `border`, `rounded`, `shadow` or `ring`, or has a `dark:` variant. Text wrapping, overflow and table layout classes (`text-wrap`, `text-ellipsis`, `border-collapse`) are layout and do not warn.

Alignment classes (`text-center`) warn too: set alignment on a parent element.

## Types

### `ComboboxItem`

An item to pick: a string, which is both what shows and what submits, or an object with the `value` that submits and the `label` that shows. `disabled` items show but can't be picked.

```ts
type ComboboxItem = string | { value: string; label: string; disabled?: boolean | undefined };
```

### `ErrorMessage`

One error message: text, or a React element for a message that carries a link or other markup.

```ts
type ErrorMessage = string | ReactElement;
```

### `ReadonlyErrorMessages`

A read-only message view accepted by fields and Form; mutable input arrays remain valid.

```ts
type ReadonlyErrorMessages = ErrorMessage | readonly ErrorMessage[];
```

[^layout-classes]: test:registry/ouro/components/ouro/combobox/combobox.test.tsx::Combobox > lets app layout classes win on the root verify:testExists(registry/ouro/components/ouro/combobox/combobox.test.tsx::Combobox > lets app layout classes win on the root)
