# Combobox

Choose one option from a list you can search by typing. The list is the app's data: the box never invents an option (no "Create …" row) and "No results" appears only when the options you gave, or the ones typing leaves, really are none. For search that runs on a server set `onQueryChange` and pass what came back as `options`; the box then stops filtering them itself, shows "Searching…" while `loading`, and fades the new list in once it arrives. Nothing is chosen until the person chooses. Escape closes and focus returns to the button; arrow keys move through the options and Enter chooses.

- Registry name: `combobox` (`@candor/combobox`)
- Tier: Pro. Installing it needs a Pro, Premium or Team licence key: the person runs `npx candor-ui key <key>` themselves; never ask for the key in chat or write it to a file.
- Docs: https://candor.themethodic.com/docs/combobox

## Install

```bash
npx candor-ui add combobox
```

Run `npx candor-ui@latest init` once first (https://candor.themethodic.com/get-started.md). Without the CLI: `npx shadcn@latest add @candor/combobox`.
It brings in: button, command, locale, locked, motion, popover, utils.
npm packages: lucide-react@^1.67.0.

## Example

```tsx
"use client"

import * as React from "react"
import { Combobox, type ComboboxOption } from "@/components/ui/combobox"

const regions: ComboboxOption[] = [
  { value: "eu-west", label: "Europe (Ireland)", group: "Europe", description: "3 zones" },
  { value: "eu-central", label: "Europe (Frankfurt)", group: "Europe", description: "3 zones" },
  { value: "us-east", label: "US East (Virginia)", group: "Americas", description: "6 zones" },
  { value: "us-west", label: "US West (Oregon)", group: "Americas", description: "4 zones" },
  { value: "ap-south", label: "Asia Pacific (Mumbai)", group: "Asia Pacific", locked: "Not available on your plan." },
]

// Nothing is chosen until a person chooses; the list and "No results" are only what the app gives it.
export default function Example() {
  const [region, setRegion] = React.useState<string | null>(null)
  return (
    <div className="flex flex-col items-start gap-2">
      <label htmlFor="region" className="text-sm font-medium">
        Region
      </label>
      <Combobox id="region" options={regions} value={region} onValueChange={setRegion} placeholder="Select a region" searchPlaceholder="Search regions" emptyMessage="No region matches that." />
    </div>
  )
}
```

## <Combobox>

Props:

- `options: ComboboxOption[]`: The choices.
- `value?: string | null | undefined`: The chosen option's `value`, when you control it. `null` is nothing chosen.
- `defaultValue?: string | undefined`: The option chosen at first, when you don't control it. Left out, nothing is chosen.
- `onValueChange?: ((value: string) => void) | undefined`: Called with the option's `value` when a person chooses one.
- `query?: string | undefined`: The search text, when you control it (for server search).
- `onQueryChange?: ((query: string) => void) | undefined`: Called as the person types, and with "" when the popover closes. Setting it means you search: the box won't filter `options` itself unless you set `shouldFilter`.
- `loading?: boolean | undefined` (default `false`): A search is in flight: shows "Searching…" in place of the list's empty message.
- `shouldFilter?: boolean | undefined`: Whether the box narrows `options` by what is typed. Defaults to true, or false when `onQueryChange` is set.
- `placeholder?: string | undefined`: Text on the button while nothing is chosen. Defaults to "Select…".
- `searchPlaceholder?: string | undefined`: Placeholder in the search field. Defaults to "Search…".
- `emptyMessage?: string | undefined`: The words when there are no options, in your own terms ("No members match “ana”."). Defaults to "No results.".
- `locked?: string | null | undefined`: Why the combobox can't be used right now. The button stays focusable and says why. Needs a `TooltipProvider`.
- `id?: string | undefined`
- `className?: string | undefined`
- `"aria-label"?: string | undefined`: The button's accessible name when it needs one beyond its text (the field's label).

## Strings (`combobox`)

The fixed words, English by default. Override any of them under `combobox` in `<LocaleProvider strings={{ combobox: { … } }}>`; the table is exported as `comboboxStrings`.

- `placeholder`: Select…
- `search`: Search…
- `empty`: No results.
- `loading`: Searching…
- `selected`: Selected

## Types

### ComboboxOption

One choice.

```ts
{
  /** What `onValueChange` receives. Typing also matches it, so use something a person would recognise or add `keywords`. */
  value: string;
  /** What the person reads and what typing matches. */
  label: string;
  /** A second line under the label. */
  description?: React.ReactNode | undefined;
  /** Extra words that find this option when typed. */
  keywords?: string[] | undefined;
  /** A heading to list this option under. Options with the same group sit together, in the order they first appear. */
  group?: string | undefined;
  /** Why this option can't be chosen. Printed in the option, which stays in the list. */
  locked?: string | null | undefined;
}
```

## Motion

- **results** (arrive, fade): The list fades in when the options the app hands over change after the list first drew, such as a search that has come back. The first list, and a list the box only filters as the person types, appears without it.

Set with `--cd-motion-combobox-<moment>-*` in your brand file; the families are in the design reference.

## Rules for using Candor UI

- Components take plain typed data. Never pass model output as HTML or render it as a link; messages are typed blocks.
- A control that can't be used says why: wrap it in `<Locked reason="…">`, don't just disable it.
- Don't show a value you don't have: leave it out (it renders as —) rather than guess. An estimate is labelled as one.
- Nothing is pre-selected in a decision a person makes, and the agent is named from your app's data, never from model output.
- Your app's words go in `LocaleProvider` (fixed strings) and `TermsProvider` (nouns); entity kinds and agent roles in `EntityKindsProvider` and `AgentRolesProvider`.
