Combobox

An input with a filterable list of options — pick one, or several as chips.

Nothing selected yet.

'use client'

import * as React from 'react'
import {
  Combobox,
  ComboboxInput,
  ComboboxContent,
  ComboboxEmpty,
  ComboboxList,
  ComboboxItem,
} from '@/components/ui/combobox'
import { Label } from '@/components/ui/label'

interface Framework {
  value: string
  label: string
}

const frameworks: Framework[] = [
  { value: 'next', label: 'Next.js' },
  { value: 'sveltekit', label: 'SvelteKit' },
  { value: 'nuxt', label: 'Nuxt' },
  { value: 'remix', label: 'Remix' },
  { value: 'astro', label: 'Astro' },
  { value: 'solidstart', label: 'SolidStart' },
  { value: 'qwik', label: 'Qwik City' },
  { value: 'eleventy', label: 'Eleventy' },
]

export default function ComboboxDemo() {
  const [value, setValue] = React.useState<Framework | null>(null)

  return (
    <div className="grid w-full max-w-xs gap-3">
      <Combobox items={frameworks} value={value} onValueChange={setValue}>
        <div className="grid gap-1.5">
          <Label htmlFor="combobox-framework">Framework</Label>
          <ComboboxInput id="combobox-framework" placeholder="Search frameworks…" showClear />
        </div>
        <ComboboxContent>
          <ComboboxEmpty>No framework found.</ComboboxEmpty>
          <ComboboxList>
            {(item: Framework) => (
              <ComboboxItem key={item.value} value={item}>
                {item.label}
              </ComboboxItem>
            )}
          </ComboboxList>
        </ComboboxContent>
      </Combobox>
      <p className="text-sm text-muted-foreground" aria-live="polite">
        {value ? `Selected: ${value.label}` : 'Nothing selected yet.'}
      </p>
    </div>
  )
}

Built on Base UI

Installation

pnpm dlx shadcn@latest add https://kagadmodyaa.vercel.app/r/combobox.json

Needs a React + Tailwind project set up with shadcn. Copies the component source straight into your project, so you can edit it.

Combobox imports cn() from lib/utils.ts — add it too if you don't have it yet:

import { clsx, type ClassValue } from 'clsx'
import { twMerge } from 'tailwind-merge'

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs))
}

Usage

import {
  Combobox,
  ComboboxInput,
  ComboboxContent,
  ComboboxEmpty,
  ComboboxList,
  ComboboxItem,
} from '@/components/ui/combobox'

const frameworks = [
  { value: 'next', label: 'Next.js' },
  { value: 'astro', label: 'Astro' },
]

<Combobox items={frameworks} onValueChange={choose}>
  <ComboboxInput aria-label="Framework" placeholder="Search frameworks…" />
  <ComboboxContent>
    <ComboboxEmpty>No framework found.</ComboboxEmpty>
    <ComboboxList>
      {item => (
        <ComboboxItem key={item.value} value={item}>
          {item.label}
        </ComboboxItem>
      )}
    </ComboboxList>
  </ComboboxContent>
</Combobox>

Examples

Multiple selection

'use client'

import {
  Combobox,
  ComboboxValue,
  ComboboxChips,
  ComboboxChip,
  ComboboxChipsInput,
  ComboboxContent,
  ComboboxEmpty,
  ComboboxList,
  ComboboxItem,
} from '@/components/ui/combobox'
import { Label } from '@/components/ui/label'

interface Language {
  value: string
  label: string
}

const languages: Language[] = [
  { value: 'ts', label: 'TypeScript' },
  { value: 'js', label: 'JavaScript' },
  { value: 'py', label: 'Python' },
  { value: 'go', label: 'Go' },
  { value: 'rust', label: 'Rust' },
  { value: 'swift', label: 'Swift' },
  { value: 'kotlin', label: 'Kotlin' },
  { value: 'ruby', label: 'Ruby' },
]

export default function ComboboxMultiple() {
  return (
    <Combobox items={languages} multiple defaultValue={[languages[0], languages[2]]}>
      <div className="grid w-full max-w-sm gap-1.5">
        <Label htmlFor="combobox-languages">Languages</Label>
        <ComboboxChips>
          <ComboboxValue>
            {(selected: Language[]) => (
              <>
                {selected.map(language => (
                  <ComboboxChip key={language.value}>{language.label}</ComboboxChip>
                ))}
                <ComboboxChipsInput
                  id="combobox-languages"
                  placeholder={selected.length > 0 ? '' : 'Pick languages…'}
                />
              </>
            )}
          </ComboboxValue>
        </ComboboxChips>
      </div>
      <ComboboxContent>
        <ComboboxEmpty>No language found.</ComboboxEmpty>
        <ComboboxList>
          {(item: Language) => (
            <ComboboxItem key={item.value} value={item}>
              {item.label}
            </ComboboxItem>
          )}
        </ComboboxList>
      </ComboboxContent>
    </Combobox>
  )
}

API Reference

NameTypeDefaultDescription
itemsItem[] | { value: string; items: Item[] }[]—The options, flat or in groups. Objects shaped { value, label } are searched and shown by label. Filtering is built in.
value / onValueChangeItem | null / (value) => void—Controlled selection. With multiple, the value is an array.
defaultValueItem | Item[] | null—Initial selection (uncontrolled).
multiplebooleanfalseLets several options be selected. Render the field with ComboboxChips.
inputValue / onInputValueChangestring / (value: string) => void—Control the text in the field.
filter(item, query, itemToString?) => boolean—Replace the default match (case- and accent-insensitive "contains"). Pass null to disable filtering.
itemToStringLabel(item: Item) => string—Turns an item into its label when it is not shaped { value, label }.
isItemEqualToValue(item: Item, value: Item) => boolean—Compare items that are equal but not the same object.
open / onOpenChangeboolean / (open: boolean) => void—Control whether the list is open.
disabled / readOnly / requiredbooleanfalseStandard form-control states. Also accepts name for form submission.
ComboboxInput showTriggerbooleantrueShows the chevron button that opens the list.
ComboboxInput showClearbooleanfalseShows a button that clears the selection while one is set.
ComboboxContent side / align / sideOffset'top' | 'bottom' / 'start' | 'center' | 'end' / number'bottom' / 'start' / 6Where the list appears relative to the field. It is as wide as the field.
ComboboxList children(item, index) => ReactNode—A render function, called for each visible option.
ComboboxEmpty——Shown only while nothing matches. Render it before ComboboxList.
ComboboxChips / ComboboxChip / ComboboxChipsInput——The field for multiple selection: render a chip per selected value and the input after them. Backspace removes the last chip; the left arrow moves onto the chips.
Keyboard——ArrowDown opens the list and moves the highlight, Enter selects, Escape closes, and typing filters.