ComponentsCombobox
Combobox
Let users search a list and choose an option.
On this page
Use this component from the local workspace package. Follow the setup guide first. A public npm package and registry are not available yet.
View registry filesImport the component into your page or component file.
import { Combobox, ComboboxItem } from "@dethink/components";Try the examples, then open the code to use them in your app. Type to filter, use the arrow keys to move through matches, and Enter to select.
Basic
Static options filter against the typed text automatically.
Show sourceexamples/combobox/basic.tsx
"use client";
import { Combobox, ComboboxItem } from "@dethink/components";
export function ComboboxBasic() {
return (
<div className="mx-auto max-w-xs">
<Combobox
label="Assignee"
placeholder="Type to filter people"
description="Filtering matches as you type."
name="assignee"
>
<ComboboxItem value="amara">Amara Okafor</ComboboxItem>
<ComboboxItem value="jonas">Jonas Weber</ComboboxItem>
<ComboboxItem value="mei">Mei Tanaka</ComboboxItem>
<ComboboxItem value="ravi">Ravi Sharma</ComboboxItem>
<ComboboxItem value="sofia">Sofía Delgado</ComboboxItem>
</Combobox>
</div>
);
}Rich options
Dynamic items with rich option layouts; textValue keeps filtering and announcement working, and menuTrigger opens the list on focus.
Show sourceexamples/combobox/rich-options.tsx
"use client";
import { Combobox, ComboboxItem } from "@dethink/components";
const repositories = [
{ value: "dethink/components", stars: "2.4k", language: "TypeScript" },
{ value: "dethink/registry", stars: "830", language: "TypeScript" },
{ value: "dethink/docs", stars: "410", language: "MDX" },
{ value: "dethink/examples", stars: "220", language: "TypeScript" },
];
export function ComboboxRichOptions() {
return (
<div className="mx-auto max-w-sm">
<Combobox
label="Repository"
placeholder="Search repositories"
items={repositories}
menuTrigger="focus"
>
{(repo) => (
<ComboboxItem
key={repo.value}
value={repo.value}
textValue={repo.value}
>
<span className="flex w-full items-baseline justify-between gap-3">
<span className="font-mono text-sm">{repo.value}</span>
<span className="text-muted-foreground text-xs">
{repo.language} · ★ {repo.stars}
</span>
</span>
</ComboboxItem>
)}
</Combobox>
</div>
);
}Custom values
allowsCustomValue keeps free text that matches no option, and formValue submits the typed text instead of an option key — the classic create-a-new-tag pattern.
Will apply: bug
Show sourceexamples/combobox/custom-value.tsx
"use client";
import { useState } from "react";
import { Combobox, ComboboxItem } from "@dethink/components";
export function ComboboxCustomValue() {
const [label, setLabel] = useState<string | null>("bug");
return (
<div className="mx-auto max-w-xs space-y-2">
<Combobox
label="Label"
description="Pick an existing label or type a new one."
allowsCustomValue
formValue="text"
defaultInputValue="bug"
onValueChange={setLabel}
onInputValueChange={(text) => setLabel(text || null)}
>
<ComboboxItem value="bug">bug</ComboboxItem>
<ComboboxItem value="enhancement">enhancement</ComboboxItem>
<ComboboxItem value="documentation">documentation</ComboboxItem>
<ComboboxItem value="good-first-issue">good first issue</ComboboxItem>
</Combobox>
<p className="text-muted-foreground text-sm">
Will apply: {label ?? "nothing"}
</p>
</div>
);
}Form states
Required, disabled, read-only, and invalid with an error message.
Show sourceexamples/combobox/states.tsx
"use client";
import { Combobox, ComboboxItem } from "@dethink/components";
const environments = [
{ value: "production", label: "Production" },
{ value: "staging", label: "Staging" },
{ value: "sandbox", label: "Sandbox" },
];
export function ComboboxStates() {
return (
<div className="mx-auto grid max-w-md gap-5 sm:grid-cols-2">
<Combobox label="Required" required items={environments}>
{(item) => (
<ComboboxItem key={item.value} value={item.value}>
{item.label}
</ComboboxItem>
)}
</Combobox>
<Combobox
label="Disabled"
disabled
defaultValue="staging"
items={environments}
>
{(item) => (
<ComboboxItem key={item.value} value={item.value}>
{item.label}
</ComboboxItem>
)}
</Combobox>
<Combobox
label="Read-only"
readOnly
defaultValue="production"
items={environments}
>
{(item) => (
<ComboboxItem key={item.value} value={item.value}>
{item.label}
</ComboboxItem>
)}
</Combobox>
<Combobox
label="Invalid"
invalid
errorMessage="You do not have access to this environment."
defaultValue="production"
items={environments}
>
{(item) => (
<ComboboxItem key={item.value} value={item.value}>
{item.label}
</ComboboxItem>
)}
</Combobox>
</div>
);
}Examples that combine components for common tasks.
Command palette
A command palette is a Combobox wearing different content: grouped commands with shortcut hints, selection running the command, and the input resetting for the next run. A live region announces what ran.
No command run yet.
Show sourceexamples/combobox/recipe-command-palette.tsx
"use client";
import { useState } from "react";
import { Combobox, ComboboxItem } from "@dethink/components";
const commands = [
{
value: "new-project",
label: "Create new project…",
shortcut: "⌘N",
group: "Actions",
},
{
value: "invite",
label: "Invite teammate…",
shortcut: "⌘I",
group: "Actions",
},
{
value: "toggle-theme",
label: "Toggle color scheme",
shortcut: "⌘⇧L",
group: "Actions",
},
{
value: "goto-dashboard",
label: "Go to dashboard",
shortcut: "G D",
group: "Navigate",
},
{
value: "goto-billing",
label: "Go to billing",
shortcut: "G B",
group: "Navigate",
},
{
value: "goto-settings",
label: "Go to settings",
shortcut: "G S",
group: "Navigate",
},
];
/**
* A command palette is just a Combobox wearing different content: filtering,
* keyboard navigation, and announcement come built in. Selecting a command
* runs it and resets the input so the palette is ready for the next run.
*/
export function ComboboxRecipeCommandPalette() {
const [inputValue, setInputValue] = useState("");
const [lastCommand, setLastCommand] = useState<string | null>(null);
return (
<div className="mx-auto max-w-sm space-y-3">
<Combobox
aria-label="Command palette"
placeholder="Type a command or search…"
menuTrigger="focus"
items={commands}
inputValue={inputValue}
onInputValueChange={setInputValue}
value={null}
onValueChange={(value) => {
if (!value) {
return;
}
const command = commands.find((entry) => entry.value === value);
setLastCommand(command?.label ?? value);
setInputValue("");
}}
>
{(command) => (
<ComboboxItem
key={command.value}
value={command.value}
textValue={command.label}
>
<span className="flex w-full items-center justify-between gap-4">
<span className="flex items-baseline gap-2">
<span className="text-muted-foreground text-xs tracking-wide uppercase">
{command.group}
</span>
{command.label}
</span>
<kbd className="border-border bg-muted text-muted-foreground rounded border px-1.5 font-mono text-xs">
{command.shortcut}
</kbd>
</span>
</ComboboxItem>
)}
</Combobox>
<p aria-live="polite" className="text-muted-foreground text-sm">
{lastCommand ? `Ran: ${lastCommand}` : "No command run yet."}
</p>
</div>
);
}Combobox owns its field anatomy — pass a label instead of wrapping it in an external one. ComboboxItem takes the same value/textValue/disabled props as SelectItem.
| Prop | What it does | Default |
|---|---|---|
valuestring | null | The selected value. Use with onValueChange when your app manages the selection. | Not set |
defaultValuestring | null | The starting selection when the component manages its own state. | Not set |
onValueChange(value: string | null) => void | Called with the selected value, or null when the selection is cleared. | Not set |
inputValue / defaultInputValue / onInputValueChangestring / string / (text) => void | Use inputValue with onInputValueChange to manage the search text, or defaultInputValue to set its starting text. | Not set |
childrenComboboxItem nodes | (item) => ComboboxItem | Add ComboboxItem elements, or a function that renders each entry in items. | Not set |
items / defaultItemsIterable<ComboboxItemData> | The available options. Use defaultItems for built-in filtering, or items when your app filters the list. | Not set |
menuTrigger"input" | "focus" | "manual" | Sets when the list opens: on typing, on focus, or only from the button. | "input" |
allowsCustomValueboolean | Keeps free text that matches no option as the value. | false |
formValue"key" | "text" | Chooses whether the form submits the selected option value or the typed text. | "key" |
disabledKeysIterable<string> | The values of options users cannot select. | Not set |
label / description / errorMessageReactNode | The field label, help text, and error message. | Not set |
placeholderstring | Input placeholder before any text is typed. | Not set |
controlSize"sm" | "md" | "lg" | Sets the input height and text size. | "md" |
required / disabled / readOnly / invalidboolean | Makes the field required, disabled, read-only, or invalid. | false |
namestring | The field name used when submitting a form. | Not set |