Combobox
Autocomplete select — a Command palette on a hard-shadow popover with a mono search and a signal check on the active option. Controlled via `value`/`onValueChange`, so it drops straight into a Form field.
"use client";import * as React from "react";import { Combobox } from "@/registry/okibi/ui/combobox";const SECTORS = [ { value: "ALPHA", label: "Alpha" }, { value: "BRAVO", label: "Bravo" }, { value: "CHARLIE", label: "Charlie" }, { value: "DELTA", label: "Delta" }, { value: "ECHO", label: "Echo" },];export default function ComboboxDemo() { const [value, setValue] = React.useState(""); return ( <Combobox options={SECTORS} value={value} onValueChange={setValue} placeholder="Select sector" searchPlaceholder="Search sector…" /> );}npx shadcn@latest add https://okibi.cndr.dev/r/combobox.jsonInstallation
Install the theme first, then add the component:
npx shadcn@latest add https://okibi.cndr.dev/r/combobox.jsonInstall the required dependencies:
npm install lucide-reactAdd the okibi building blocks it composes: button, command, popover.
Copy the source from the Code tab above into components/ui/combobox.tsx. It uses the cn helper and the okibi theme tokens — install the theme first if you haven't.
Usage
import { Combobox } from "@/components/ui/combobox"
const [value, setValue] = React.useState("")
<Combobox
options={[
{ value: "alpha", label: "Alpha" },
{ value: "bravo", label: "Bravo" },
]}
value={value}
onValueChange={setValue}
placeholder="Select sector"
/>Anatomy
// A single prop-driven component — it composes a Popover trigger over a
// Command palette internally, so there are no sub-parts to assemble.
<Combobox
options={[{ value: "alpha", label: "Alpha" }]} // value drives state, label drives display + search
value={value} // controlled selection
onValueChange={setValue} // empty string clears
placeholder="Select sector" // trigger text when empty
searchPlaceholder="Search…" // command input placeholder
emptyText="No results." // shown when nothing matches
id="sector" // pair with an external <Label htmlFor>
/>Examples
Disabled
A disabled trigger locks the control on its committed value; individual options can be disabled too.
"use client";import * as React from "react";import { Combobox } from "@/registry/okibi/ui/combobox";const SECTORS = [ { value: "ALPHA", label: "Alpha" }, { value: "BRAVO", label: "Bravo" }, { value: "CHARLIE", label: "Charlie" }, { value: "DELTA", label: "Delta" }, { value: "ECHO", label: "Echo" },];export default function ComboboxDisabledDemo() { // Locked controls: a disabled combobox holding a committed value, plus an // empty one. Neither opens its palette. const [value, setValue] = React.useState("CHARLIE"); return ( <div className="flex flex-col gap-3"> <Combobox options={SECTORS} value={value} onValueChange={setValue} placeholder="Select sector" searchPlaceholder="Search sector…" disabled /> <Combobox options={SECTORS} value="" placeholder="Select sector" searchPlaceholder="Search sector…" disabled /> </div> );}Long list
Over a long option set the command palette stays searchable and scrolls inside the popover.
"use client";import * as React from "react";import { Combobox } from "@/registry/okibi/ui/combobox";// 36 entries — long enough that the command list scrolls and the search field// inside the popover earns its keep.const STATIONS = [ "Andromeda", "Antares", "Arcturus", "Bellatrix", "Betelgeuse", "Canopus", "Capella", "Castor", "Cygnus", "Deneb", "Draco", "Electra", "Fomalhaut", "Gemini", "Hadar", "Hydra", "Izar", "Kepler", "Lyra", "Mizar", "Nashira", "Orion", "Pollux", "Procyon", "Quasar", "Regulus", "Rigel", "Sirius", "Spica", "Tarazed", "Tucana", "Ursa", "Vega", "Vela", "Wezen", "Zosma",].map((name) => ({ value: name.toUpperCase(), label: name }));export default function ComboboxLongDemo() { const [value, setValue] = React.useState(""); return ( <Combobox options={STATIONS} value={value} onValueChange={setValue} placeholder="Select station" searchPlaceholder="Search station…" emptyText="No station." /> );}API Reference
Combobox
| Prop | Type | Default | Description |
|---|---|---|---|
options | { value: string; label: string }[] | – | The selectable options. |
value | string | – | Selected option value (controlled). |
onValueChange | (value: string) => void | – | Fires on select (empty string when cleared). |
placeholder | string | "Select option" | Trigger text when empty. |
searchPlaceholder | string | "Search…" | Placeholder for the search input. |
emptyText | string | "No results." | Shown when the search matches nothing. |
disabled | boolean | false | Disable the trigger. |
Accessibility
- The trigger carries
role='combobox'andaria-expanded, so its collapsed/expanded state is announced. - Built on the
Commandpalette: type-ahead filtering, arrow-key navigation,Enterto select, andEscto close all come for free. - Filtering matches on each option's
labelviakeywords, so users search by what they see, not the underlyingvalue. - The standalone trigger has no accessible name on its own — supply an
idand an externalLabel htmlForto name it. - The active option is marked with a
CheckIcon; selection isn't conveyed by the icon alone, since the label text stays in the trigger.
Guidelines
Do
- Give every option a human
labeland a stablevalue; the label drives both display and search. - Customize
searchPlaceholderandemptyTextto match the data set so the empty state reads naturally. - Override the fixed
w-56width throughclassNamewhen labels are long.
Don't
- Don't expect a separate clear control — re-selecting the active option emits an empty string to clear it; document that for users.
- Don't reach for it for free-text entry or multi-select; it's a single-select picker over a known option list.
Date Picker
An outline-tech trigger that reads the chosen date in a mono readout and opens the Calendar on a hard-shadow popover. Controlled via `value`/`onValueChange`, so it drops straight into a Form field.
Input OTP
Segmented one-time-code field with sharp mono cells, a 2px signal focus ring on the active slot, and a blinking block caret.