# Combobox

> A text field that filters a list of options as you type and picks one, with optional create.

Category: Forms. Docs page: https://rime.mjsons.net/components/combobox

## Installation

```bash
npx shadcn@latest add KezLahd/rime/combobox
```

Combobox is the editable combobox: typing opens a frosted list and narrows it by label, description and keywords. It suits long lists people know the name of (a project, a country, a customer). For a short fixed list a person compares, use Select; Select's searchable mode is the same idea with a button trigger.

Leaving the field restores the chosen option's label, so the text never disagrees with the value. creatable offers "Create …" when the text matches nothing, and hands the text to onCreate.

inline renders the list open in normal flow under the field instead of a portalled popover: for docs, previews and pickers that live inside a panel.

## Usage

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

<Field label="Project">
  <Combobox options={PROJECTS} value={project} onChange={setProject} placeholder="Type to search projects" />
</Field>
```

## Props: Combobox

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `options` (required) | `ReadonlyArray<ComboboxOption>` |  |  |
| `value` (required) | `string \| null` |  |  |
| `onChange` (required) | `(value: string \| null) => void` |  | The chosen option's value, or null when the text is cleared. |
| `placeholder` | `string` |  |  |
| `creatable` | `boolean` |  | Offer "Create …" when the typed text matches no option. onCreate receives the text; add the option and select it there. |
| `onCreate` | `(label: string) => void` |  |  |
| `emptyText` | `string` | `"No matches"` | Shown when nothing matches and creatable is off. |
| `size` | `"sm" \| "md"` | `"md"` |  |
| `disabled` | `boolean` |  |  |
| `invalid` | `boolean` |  |  |
| `id` | `string` |  |  |
| `name` | `string` |  |  |
| `aria-label` | `string` |  |  |
| `defaultOpen` | `boolean` | `false` | Open on mount. |
| `inline` | `boolean` | `false` | Render the list in normal flow under the field instead of portalling a popover, and keep it open. For docs, previews and pickers inside a panel. |
| `className` | `string` |  |  |

## Examples

### Open list

Shown inline so the list is visible; in a form it is a popover.

```tsx
<Combobox inline aria-label="Project" options={PROJECTS} value={project} onChange={setProject} />
```

### In a Field

```tsx
<Field label="Project">
  <Combobox options={PROJECTS} value={project} onChange={setProject} placeholder="Type to search projects" />
</Field>
```

### Creatable

```tsx
<Combobox
  creatable
  options={tags}
  value={tag}
  onChange={setTag}
  onCreate={(label) => {
    const value = slugify(label);
    setTags((t) => [...t, { value, label }]);
    setTag(value);
  }}
/>
```

## Theme tokens

Per-component tokens. Undeclared by default: the default below is the fallback the CSS uses. Set any of them at :root or on a wrapper to restyle every instance.

| Token | Default |
| --- | --- |
| `--field-focus-border` | `rgba(var(--rgb-brand), 0.7)` / `var(--focus-color)` |
| `--field-focus-ring` | `var(--focus-halo)` |
| `--field-focus-ring-invalid` | `0 0 0 4px rgba(var(--rgb-danger), 0.14)` |
| `--field-hover-border` | `rgba(var(--rgb-brand-deep), 0.3)` |
| `--input-bg` | `var(--surface-solid)` |
| `--input-border` | `var(--line-input)` |
| `--input-border-width` | `var(--border-w-field)` |
| `--input-font-size` | `var(--text-input)` |
| `--input-height-md` | `var(--field-h-md)` |
| `--input-height-sm` | `var(--field-h-sm)` |
| `--input-pad-x` | `calc(14px * var(--density))` |
| `--input-radius` | `var(--r-md)` / `var(--r-sm)` |
| `--input-shadow` | `var(--shadow-input)` |
| `--popover-item-active-bg` | `linear-gradient(to right, rgba(var(--rgb-brand), 0.14), rgba(var(--rgb-brand-deep), 0.07))` |
| `--popover-item-radius` | `var(--r-xs)` |
| `--popover-pad` | `calc(6px * var(--density))` |
| `--popover-radius` | `var(--r-lg)` |

System tokens it reads: `--border-w`, `--border-w-field`, `--brand`, `--danger-base`, `--danger-field-fill`, `--density`, `--dur`, `--dur-fast`, `--ease`, `--ease-out`, `--field-h-md`, `--field-h-sm`, `--focus-color`, `--focus-halo`, `--font-body`, `--font-mono`, `--ink-body`, `--ink-brand`, `--ink-disabled`, `--ink-muted`, `--ink-placeholder`, `--ink-secondary`, `--line-input`, `--line-soft`, `--popover-blur`, `--popover-border`, `--popover-shadow`, `--popover-surface`, `--r-lg`, `--r-md`, `--r-sm`, `--r-xs`, `--rgb-brand`, `--rgb-brand-deep`, `--rgb-danger`, `--scrollbar-thumb`, `--scrollbar-track`, `--scrollbar-width`, `--shadow-input`, `--surface-solid`, `--surface-sunken`, `--text-body`, `--text-caption`, `--text-input`, `--text-small`, `--wash-subtle`, `--z-popover`.

## Accessibility

- The input is role="combobox" with aria-expanded, aria-controls, aria-autocomplete="list" and aria-activedescendant, so focus stays in the field while Up and Down move the highlight.
- Keyboard: typing filters, Up and Down move (and open), Home and End jump, Enter picks, Esc closes and then clears, Tab closes and moves on.
- The number of results is announced through a polite status region as you type.
- Give it a Field label, or aria-label when it stands alone.

## Do

- Search descriptions and keywords, not only labels.
- Use creatable only where adding a value is safe and expected (tags).

## Don't

- Don't use Combobox for fewer than about eight options; use Select or RadioGroup.
- Don't allow free text that is not saved as an option; that is a TextInput.

Related: https://rime.mjsons.net/components/select, https://rime.mjsons.net/components/command, https://rime.mjsons.net/components/field
