Unified UI

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 combobox
pnpm dlx @work-rjkashyap/unified-ui add combobox
npx @work-rjkashyap/unified-ui add combobox
bunx @work-rjkashyap/unified-ui add combobox

If 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-ui
pnpm add @work-rjkashyap/unified-ui
yarn add @work-rjkashyap/unified-ui
bun add @work-rjkashyap/unified-ui

Anatomy

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

SizeHeightUse Case
sm32pxCompact toolbars, sidebars
md36pxDefault — most contexts
lg40pxProminent 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

PropTypeDefaultDescription
optionsComboboxOption[]The list of options to display.
groupsComboboxGroup[]Grouped section definitions.
valuestringSelected value (single mode, controlled).
valuesstring[]Selected values (multi mode, controlled).
defaultValuestringDefault value (single mode, uncontrolled).
defaultValuesstring[]Default values (multi mode, uncontrolled).
onSelect(value: string | null) => voidCalled when a single value is selected/deselected.
onMultiSelect(values: string[]) => voidCalled when multi-select values change.
multibooleanfalseEnable multi-select mode.
searchablebooleantrueEnable search / filter input.
placeholderstring"Select..."Placeholder text on the trigger.
searchPlaceholderstring"Search..."Placeholder text in the search input.
emptyMessagestring"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.
disabledbooleanfalseDisables the combobox.
clearablebooleantrueShow a clear button when a value is selected.
maxHeightstring"240px"Max height of the dropdown list (CSS value).
filterOption(option: ComboboxOption, query: string) => booleanCase-insensitiveCustom filter function.
renderOption(option: ComboboxOption, isSelected: boolean) => ReactNodeCustom render for option items.
renderValue(selected: ComboboxOption | ComboboxOption[] | null) => ReactNodeCustom render for the trigger value display.
align"start" | "center" | "end""start"Alignment of the dropdown relative to the trigger.
matchWidthbooleantrueWhether the dropdown width matches the trigger width.
classNamestringAdditional CSS classes on the trigger.
contentClassNamestringAdditional CSS classes on the dropdown content.

ComboboxOption

PropertyTypeDescription
valuestringUnique key for this option.
labelstringDisplay label.
descriptionstring?Optional description shown below the label.
iconReactNode?Optional leading icon/element.
disabledboolean?Whether this option is disabled.
groupstring?Group key — options with the same group render together.

ComboboxGroup

PropertyTypeDescription
valuestringGroup key.
labelstringGroup section label.

Motion

  • Dropdown open/close — Uses the scaleIn preset (scale + opacity) via AnimatePresence for smooth enter/exit.
  • Results list — Stagger animation with staggerContainerFast and slideUpSm per item for a cascading reveal.
  • Empty state — Fades in with the fadeIn preset when no results match.
  • Multi-select tags — Animate in/out with scale transitions on add/remove.
  • All animations respect prefers-reduced-motion via useReducedMotion().

Keyboard Navigation

KeyAction
Enter / SpaceOpen dropdown (when trigger is focused).
EscapeClose dropdown.
ArrowDownMove highlight to next option.
ArrowUpMove highlight to previous option.
EnterSelect the highlighted option.
HomeMove highlight to first option.
EndMove highlight to last option.
BackspaceRemove last selected tag (multi mode, empty search).
TypingFilters the visible options in real-time.

Accessibility

  • The trigger uses role="combobox" with aria-expanded, aria-haspopup="listbox", and aria-controls.
  • The dropdown uses role="listbox" with aria-label.
  • Each option uses role="option" with aria-selected and aria-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-label for their remove button.

Design Tokens

TokenUsage
--inputTrigger border color (default variant)
--primaryTrigger border color (primary variant)
--ringFocus ring and open-state border
--borderDropdown border, separator lines
--backgroundTrigger and dropdown background
--accentOption hover/active highlight
--mutedSearch input background
--radius-mdTrigger and option border radius
--radius-lgDropdown border radius
--duration-fastTransition speed

On this page