Combobox

An advanced autocomplete/combobox component built with Headless UI.

Import

import { Combobox, ComboboxOption, ComboboxLabel, ComboboxDescription } from '@/components/ui';

Basic Usage

'use client';
import { useState } from 'react';
import { Combobox, ComboboxOption, ComboboxLabel } from '@/components/ui';

const options = [
  { id: 1, name: 'Wade Cooper' },
  { id: 2, name: 'Arlene Mccoy' },
  { id: 3, name: 'Devon Webb' },
];

export function Example() {
  const [selected, setSelected] = useState(null);

  return (
    <Combobox
      value={selected}
      onChange={setSelected}
      options={options}
      displayValue={(option) => option?.name}
      placeholder="Search people..."
    >
      {(option) => (
        <ComboboxOption value={option}>
          <ComboboxLabel>{option.name}</ComboboxLabel>
        </ComboboxOption>
      )}
    </Combobox>
  );
}

Props

Combobox

Prop Type Default Description
value T - Selected value
onChange (value: T) => void - Change handler
options T[] required Available options
displayValue (value: T) => string required Display value getter
filter (value: T, query: string) => boolean - Custom filter function
placeholder string - Placeholder text
anchor 'top' | 'bottom' 'bottom' Dropdown position
autoFocus boolean false Auto focus on mount
disabled boolean false Disable combobox
aria-label string - Accessibility label

ComboboxOption

Prop Type Description
value T Option value
disabled boolean Disable option

With Description

const users = [
  { id: 1, name: 'Wade Cooper', email: 'wade@example.com' },
  { id: 2, name: 'Arlene Mccoy', email: 'arlene@example.com' },
];

<Combobox
  value={selected}
  onChange={setSelected}
  options={users}
  displayValue={(user) => user?.name}
  placeholder="Search users..."
>
  {(user) => (
    <ComboboxOption value={user}>
      <ComboboxLabel>{user.name}</ComboboxLabel>
      <ComboboxDescription>{user.email}</ComboboxDescription>
    </ComboboxOption>
  )}
</Combobox>

Custom Filter

<Combobox
  value={selected}
  onChange={setSelected}
  options={users}
  displayValue={(user) => user?.name}
  filter={(user, query) =>
    user.name.toLowerCase().includes(query.toLowerCase()) ||
    user.email.toLowerCase().includes(query.toLowerCase())
  }
  placeholder="Search by name or email..."
>
  {(user) => (
    <ComboboxOption value={user}>
      <ComboboxLabel>{user.name}</ComboboxLabel>
    </ComboboxOption>
  )}
</Combobox>

With Icons

import { UserIcon } from '@/components/icons';

<Combobox
  value={selected}
  onChange={setSelected}
  options={users}
  displayValue={(user) => user?.name}
>
  {(user) => (
    <ComboboxOption value={user}>
      <UserIcon data-slot="icon" />
      <ComboboxLabel>{user.name}</ComboboxLabel>
    </ComboboxOption>
  )}
</Combobox>

With Avatar

import { Avatar } from '@/components/ui';

<Combobox
  value={selected}
  onChange={setSelected}
  options={users}
  displayValue={(user) => user?.name}
>
  {(user) => (
    <ComboboxOption value={user}>
      <Avatar data-slot="avatar" src={user.avatar} name={user.name} size="sm" />
      <ComboboxLabel>{user.name}</ComboboxLabel>
    </ComboboxOption>
  )}
</Combobox>

Async Loading

'use client';
import { useState } from 'react';
import { useQuery } from '@tanstack/react-query';
import { Combobox, ComboboxOption, ComboboxLabel, Spinner } from '@/components/ui';

export function AsyncCombobox() {
  const [query, setQuery] = useState('');
  const [selected, setSelected] = useState(null);

  const { data: options, isLoading } = useQuery({
    queryKey: ['search', query],
    queryFn: () => searchUsers(query),
    enabled: query.length > 0,
  });

  return (
    <Combobox
      value={selected}
      onChange={setSelected}
      options={options ?? []}
      displayValue={(user) => user?.name}
      placeholder="Search users..."
    >
      {isLoading ? (
        <div className="p-4 text-center">
          <Spinner />
        </div>
      ) : (
        (user) => (
          <ComboboxOption value={user}>
            <ComboboxLabel>{user.name}</ComboboxLabel>
          </ComboboxOption>
        )
      )}
    </Combobox>
  );
}

Dropdown Position

{/* Opens below (default) */}
<Combobox anchor="bottom" ... />

{/* Opens above */}
<Combobox anchor="top" ... />

Features

When to Use

Scenario Component
Simple ID/name mapping SearchableSelect
Custom option rendering Combobox
Few options Select
Async search Combobox