Combobox
ProChoose one option, or several with multiple, 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 busy, 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.
With multiple, Enter or a click adds or removes an option and the list stays open; the button reads the chosen labels (or how many, past two) and the list ends with "Clear selection" once something is chosen. Its value is values, an array, with onValuesChange; value and onValueChange stay the single mode's.
A chosen value that isn't in the current options (a server search came back without it) reads by the label it had when it was chosen here. One the box never saw, such as a value set from outside, is counted ("1 chosen"): the button never shows the placeholder while something is chosen.
Version 5.2.0· other versions
This page is for 5.2.0, the current release. An earlier release’s page is the Markdown that release published, so the docs match the version you have installed (candor-lock.json records it).
- 5.1.0
- 5.0.0
- 4.5.0
- 4.4.0
- 4.3.0
- 4.2.0
- 4.1.0
- 4.0.2
- 4.0.1
- 4.0.0
- 3.0.0
- 2.10.0
- 2.9.0
- 2.8.0
- 2.7.0
- 2.6.2
- 2.6.1
- 2.6.0
- 2.5.1
- 2.5.0
- 2.4.0
- 2.3.0
Not in the docs of 1.3.0, 1.4.0, 1.5.0, 1.5.1, 1.6.0, 1.7.0, 1.8.0, 1.8.1, 1.9.0, 1.9.1, 2.0.0, 2.1.0 and 2.2.0: the page came later, or had another name then.
Docs pages weren’t kept for 1.0.0, 1.0.1, 1.1.0, 1.1.1, 1.2.0 and 1.2.1: they are kept from 1.3.0 on, and before that only llms.txt was published.
Preview
Loading the preview
Install
First time? Get started sets up the registry and theme. Or with shadcn directly: npx shadcn@latest add @candor/combobox
Brings in: button, command, locale, locked, motion, popover, tokens, utils · npm: lucide-react@^1.67.0
Usage
"use client"
import * as React from "react"
import { Combobox, type ComboboxOption } from "@/components/candor/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. With `multiple`
// the list stays open while the person adds and removes options.
export default function Example() {
const [region, setRegion] = React.useState<string | null>(null)
const [replicas, setReplicas] = React.useState<string[]>([])
return (
<div className="flex flex-col items-start gap-4">
<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>
<div className="flex flex-col items-start gap-2">
<label htmlFor="replicas" className="text-sm font-medium">
Read replicas
</label>
<Combobox id="replicas" multiple options={regions} values={replicas} onValuesChange={setReplicas} placeholder="Select regions" searchPlaceholder="Search regions" />
</div>
</div>
)
}<Combobox>
| Prop | Type | Default | Description |
|---|---|---|---|
| optionsrequired | 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. |
| multiple | boolean | undefined | false | Lets the person choose several options. Use `values` and `onValuesChange` for them in place of `value` and `onValueChange`. |
| values | string[] | undefined | — | With `multiple`: the chosen options' values, when you control them, in the order they were chosen. |
| defaultValues | string[] | undefined | — | With `multiple`: the options chosen at first, when you don't control them. Left out, none are. |
| onValuesChange | ((values: string[]) => void) | undefined | — | With `multiple`: called with every chosen value each time one is added or removed. |
| 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`. |
| busy | boolean | undefined | 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. |
| id | string | undefined | — | The button's id, for a `<label htmlFor>` or `Field` to point at. |
| className | string | undefined | — | Classes for the button. |
| "aria-label" | string | undefined | — | The button's accessible name when it needs one beyond its text (the field's label). |
| "aria-describedby" | string | undefined | — | Ids of the text that describes the choice: a hint, an error. `Field` sets it. |
| "aria-invalid" | boolean | "true" | "false" | undefined | — | The choice has an error: the button is outlined in the destructive colour. `Field` sets it. |
| "aria-required" | boolean | "true" | "false" | undefined | — | A choice must be made. `Field` sets it from `required`. |
Strings
The fixed words, in English by default. Change any of them under combobox in <LocaleProvider strings={{ … }}>; the table is exported as comboboxStrings.
| Key | English |
|---|---|
| placeholder | Select… |
| search | Search… |
| empty | No results. |
| loading | Searching… |
| selected | Selected |
| chosenCount | { one: "{count} chosen", other: "{count} chosen" } |
| clear | Clear selection |
Motion
| Moment | Family | What it does |
|---|---|---|
| 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.
Types
- ComboboxOption
- One choice.
{ /** 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; }