Skip to content

Shadcn Combobox

The shadcn/ui combobox is a searchable single-select built by combining a Popover, a Command menu, and a Button trigger, styled with Tailwind CSS v4. Because you compose it, you control the trigger label, filtering, and empty state. Comboboxes handle long option lists in filters and forms across ShadcnStore blocks.

Key features

  • Type-to-filter search over a long option list.
  • Single selection with a custom trigger label.
  • Keyboard navigation from the Command menu.
  • Works with static, dynamic, or async options.
  • Composed from Popover + Command + Button.

Installation

Add the pieces it composes:

bash
npx shadcn@latest add popover command button

Usage

tsx
"use client"
import { useState } from "react"
import { Check, ChevronsUpDown } from "lucide-react"
import { Button } from "@/components/ui/button"
import { Popover, PopoverTrigger, PopoverContent } from "@/components/ui/popover"
import {
  Command,
  CommandInput,
  CommandList,
  CommandEmpty,
  CommandGroup,
  CommandItem,
} from "@/components/ui/command"

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

export function Combobox() {
  const [open, setOpen] = useState(false)
  const [value, setValue] = useState("")
  const selected = frameworks.find((f) => f.value === value)

  return (
    <Popover open={open} onOpenChange={setOpen}>
      <PopoverTrigger
        render={
          <Button variant="outline" role="combobox" aria-expanded={open} />
        }
      >
        {selected ? selected.label : "Select framework…"}
        <ChevronsUpDown className="ml-2 size-4 opacity-50" />
      </PopoverTrigger>
      <PopoverContent className="w-[200px] p-0">
        <Command>
          <CommandInput placeholder="Search framework…" />
          <CommandList>
            <CommandEmpty>No framework found.</CommandEmpty>
            <CommandGroup>
              {frameworks.map((f) => (
                <CommandItem
                  key={f.value}
                  value={f.value}
                  onSelect={(v) => {
                    setValue(v === value ? "" : v)
                    setOpen(false)
                  }}
                >
                  <Check
                    className={value === f.value ? "mr-2 size-4" : "mr-2 size-4 opacity-0"}
                  />
                  {f.label}
                </CommandItem>
              ))}
            </CommandGroup>
          </CommandList>
        </Command>
      </PopoverContent>
    </Popover>
  )
}

Composition

The combobox composes Popover and Command. Use render on PopoverTrigger to style the trigger as a Button:

tsx
<PopoverTrigger render={<Button variant="outline" role="combobox" aria-expanded={open} />}>
  {selected ? selected.label : "Select framework…"}
</PopoverTrigger>

Accessibility

  • Give the trigger role="combobox" and reflect the open state with aria-expanded.
  • The Command input drives an accessible list; keep option labels unique.
  • Show a clear empty state and keep the selected value visible in the trigger.

When to use the Combobox

Use a combobox when the user picks one value from a long or searchable list: a country, a repository, a user, or a framework. For a short, fixed list use a Select. For choosing among a few always-visible options, use a Radio Group.

Used in these blocks

The combobox is built from Popover, Command, and Button, and usually lives inside a Form. For a short fixed list use a Select.

FAQ

What is the shadcn combobox?

The combobox is a searchable single-select built by combining a Popover, a Command menu, and a Button trigger, ideal for long option lists.

Is the combobox a single component?

No. shadcn provides the Popover and Command pieces and a guide to compose them, so you control the trigger and filtering.

What is the difference between Combobox and Select?

Combobox adds type-to-filter search and suits long or async lists. Select is a simpler menu for short, fixed option sets.

How do I load combobox options asynchronously?

Fetch options into state and render them as CommandItems; the Command menu filters whatever you provide.