Combobox
A searchable select / autocomplete with single and multi-select modes, grouped options, async filtering, and animated dropdown.
Basic
A production-ready searchable select / autocomplete built by composing Popover + Command primitives with Framer Motion animation. Supports single and multi-select, grouped options, custom rendering, and keyboard navigation.
Installation
Install the component via the CLI in one command.
npx @work-rjkashyap/unified-ui add comboboxpnpm dlx @work-rjkashyap/unified-ui add comboboxnpx @work-rjkashyap/unified-ui add comboboxbunx @work-rjkashyap/unified-ui add comboboxIf you haven't initialized your project yet, run npx @work-rjkashyap/unified-ui init first. See the CLI docs for details.
Or install the full package
Use this approach if you prefer to install the entire design system as a dependency instead of copying individual components.
npm install @work-rjkashyap/unified-uipnpm add @work-rjkashyap/unified-uiyarn add @work-rjkashyap/unified-uibun add @work-rjkashyap/unified-uiAnatomy
import { Combobox } from "@work-rjkashyap/unified-ui";Basic Usage
Pass an array of options with value and label fields. The component manages its own open/close and search state internally.
const frameworks = [
{ value: "react", label: "React" },
{ value: "vue", label: "Vue" },
{ value: "angular", label: "Angular" },
];
<Combobox options={frameworks} placeholder="Select framework..." />Sizes
| Size | Height | Use Case |
|---|---|---|
sm | 32px | Compact toolbars, sidebars |
md | 36px | Default — most contexts |
lg | 40px | Prominent forms, hero areas |
Variants
Multi-Select
Enable multi-select mode with the multi prop. Selected items appear as removable tags in the trigger.
Grouped Options
Organize options into visual groups using the group property on options and the groups prop for section labels.
Option with Description and Icon
Options can include description text and a leading icon element for richer display.
<Combobox
options={[
{
value: "react",
label: "React",
description: "A JavaScript library for building user interfaces",
icon: <ReactIcon className="size-4" />,
},
{
value: "vue",
label: "Vue",
description: "The progressive JavaScript framework",
icon: <VueIcon className="size-4" />,
},
]}
placeholder="Select framework..."
/>Disabled Options
Individual options can be disabled by setting disabled: true:
<Combobox
options={[
{ value: "react", label: "React" },
{ value: "vue", label: "Vue", disabled: true },
{ value: "angular", label: "Angular" },
]}
placeholder="Select..."
/>Non-Searchable
Disable the search input to use the component as a styled select:
Clearable
By default, a clear button appears when a value is selected. Set clearable={false} to disable it.
Controlled
Use value and onSelect for single-select, or values and onMultiSelect for multi-select.
// Single
const [value, setValue] = useState<string | null>(null);
<Combobox
options={options}
value={value}
onSelect={setValue}
placeholder="Select..."
/>;
// Multi
const [values, setValues] = useState<string[]>([]);
<Combobox
multi
options={options}
values={values}
onMultiSelect={setValues}
placeholder="Select..."
/>;Custom Filter
Override the default case-insensitive label match with a custom filter function:
<Combobox
options={options}
filterOption={(option, query) =>
option.label.toLowerCase().startsWith(query.toLowerCase())
}
placeholder="Starts-with filter..."
/>Custom Rendering
Use renderOption to customize how each option is displayed, and renderValue to customize the trigger display:
<Combobox
options={users}
renderOption={(option, isSelected) => (
<div className="flex items-center gap-2">
<Avatar size="xs" src={option.icon} />
<div>
<p className="text-sm font-medium">{option.label}</p>
<p className="text-xs text-muted-foreground">{option.description}</p>
</div>
</div>
)}
renderValue={(selected) =>
selected ? (
<div className="flex items-center gap-2">
<Avatar size="xs" src={selected.icon} />
<span>{selected.label}</span>
</div>
) : null
}
placeholder="Select user..."
/>In a Form
<FormField label="Assignee" description="Choose the team member to assign.">
<Combobox
options={teamMembers}
value={assignee}
onSelect={setAssignee}
placeholder="Select assignee..."
/>
</FormField>Props
| Prop | Type | Default | Description |
|---|---|---|---|
options | ComboboxOption[] | — | The list of options to display. |
groups | ComboboxGroup[] | — | Grouped section definitions. |
value | string | — | Selected value (single mode, controlled). |
values | string[] | — | Selected values (multi mode, controlled). |
defaultValue | string | — | Default value (single mode, uncontrolled). |
defaultValues | string[] | — | Default values (multi mode, uncontrolled). |
onSelect | (value: string | null) => void | — | Called when a single value is selected/deselected. |
onMultiSelect | (values: string[]) => void | — | Called when multi-select values change. |
multi | boolean | false | Enable multi-select mode. |
searchable | boolean | true | Enable search / filter input. |
placeholder | string | "Select..." | Placeholder text on the trigger. |
searchPlaceholder | string | "Search..." | Placeholder text in the search input. |
emptyMessage | string | "No results found." | Message shown when no options match the query. |
variant | "default" | "primary" | "default" | Visual variant. |
size | "sm" | "md" | "lg" | "md" | Size variant — matches Input/Button/Select heights. |
disabled | boolean | false | Disables the combobox. |
clearable | boolean | true | Show a clear button when a value is selected. |
maxHeight | string | "240px" | Max height of the dropdown list (CSS value). |
filterOption | (option: ComboboxOption, query: string) => boolean | Case-insensitive | Custom filter function. |
renderOption | (option: ComboboxOption, isSelected: boolean) => ReactNode | — | Custom render for option items. |
renderValue | (selected: ComboboxOption | ComboboxOption[] | null) => ReactNode | — | Custom render for the trigger value display. |
align | "start" | "center" | "end" | "start" | Alignment of the dropdown relative to the trigger. |
matchWidth | boolean | true | Whether the dropdown width matches the trigger width. |
className | string | — | Additional CSS classes on the trigger. |
contentClassName | string | — | Additional CSS classes on the dropdown content. |
ComboboxOption
| Property | Type | Description |
|---|---|---|
value | string | Unique key for this option. |
label | string | Display label. |
description | string? | Optional description shown below the label. |
icon | ReactNode? | Optional leading icon/element. |
disabled | boolean? | Whether this option is disabled. |
group | string? | Group key — options with the same group render together. |
ComboboxGroup
| Property | Type | Description |
|---|---|---|
value | string | Group key. |
label | string | Group section label. |
Motion
- Dropdown open/close — Uses the
scaleInpreset (scale + opacity) viaAnimatePresencefor smooth enter/exit. - Results list — Stagger animation with
staggerContainerFastandslideUpSmper item for a cascading reveal. - Empty state — Fades in with the
fadeInpreset when no results match. - Multi-select tags — Animate in/out with scale transitions on add/remove.
- All animations respect
prefers-reduced-motionviauseReducedMotion().
Keyboard Navigation
| Key | Action |
|---|---|
Enter / Space | Open dropdown (when trigger is focused). |
Escape | Close dropdown. |
ArrowDown | Move highlight to next option. |
ArrowUp | Move highlight to previous option. |
Enter | Select the highlighted option. |
Home | Move highlight to first option. |
End | Move highlight to last option. |
Backspace | Remove last selected tag (multi mode, empty search). |
| Typing | Filters the visible options in real-time. |
Accessibility
- The trigger uses
role="combobox"witharia-expanded,aria-haspopup="listbox", andaria-controls. - The dropdown uses
role="listbox"witharia-label. - Each option uses
role="option"witharia-selectedandaria-disabled. - Active descendant tracking via
aria-activedescendant. - Disabled options are excluded from keyboard navigation.
- The search input is labeled with an
aria-label. - The clear button has
aria-label="Clear selection". - Multi-select tags have
aria-labelfor their remove button.
Design Tokens
| Token | Usage |
|---|---|
--input | Trigger border color (default variant) |
--primary | Trigger border color (primary variant) |
--ring | Focus ring and open-state border |
--border | Dropdown border, separator lines |
--background | Trigger and dropdown background |
--accent | Option hover/active highlight |
--muted | Search input background |
--radius-md | Trigger and option border radius |
--radius-lg | Dropdown border radius |
--duration-fast | Transition speed |