Component guideEditable source

Combobox

Searchable single- and multiple-selection recipes.

Original component docs
On this page8 sectionsBack to top

Use these recipes when users need to search a list before selecting options. The single-selection examples compose Popover and Command; the multiple-selection example uses Base UI Combobox so selected values remain distinct from the current keyboard position. These are editable recipes, not a standalone Combobox registry endpoint.

Live preview
Loading preview...

#Installation

Complete the base setup, then install Button, Popover, and Command. Copy the Usage recipe into your application.

npx shadcn@latest add https://neobrutal-ui.andongmin.com/r/button.json https://neobrutal-ui.andongmin.com/r/popover.json https://neobrutal-ui.andongmin.com/r/command.json

The multiple-selection example only needs Button and @base-ui/react, which the shared base already installs. It does not use Command or Popover.

#Usage

"use client";
import { useState } from "react";
import { Button } from "@/components/ui/button";
import {
  Command,
  CommandEmpty,
  CommandInput,
  CommandItem,
  CommandList,
} from "@/components/ui/command";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";

const options = ["React", "Vue", "Svelte"];

export default function FrameworkPicker() {
  const [open, setOpen] = useState(false);
  const [value, setValue] = useState("");
  return (
    <Popover open={open} onOpenChange={setOpen}>
      <PopoverTrigger
        render={
          <Button variant="outline" role="combobox" aria-expanded={open} aria-label="Framework" />
        }
      >
        {value || "Choose framework"}
      </PopoverTrigger>
      <PopoverContent aria-label="Choose a framework" className="w-64 p-0">
        <Command>
          <CommandInput placeholder="Search frameworks…" aria-label="Search frameworks" />
          <CommandList>
            <CommandEmpty>No framework found.</CommandEmpty>
            {options.map((option) => (
              <CommandItem
                key={option}
                value={option}
                onSelect={() => {
                  setValue(option);
                  setOpen(false);
                }}
              >
                {option}
              </CommandItem>
            ))}
          </CommandList>
        </Command>
      </PopoverContent>
    </Popover>
  );
}

#API reference

Recipe state / partPurposeDefault
open / setOpenPopover visibilityfalse
value / setValueSelected optionEmpty string
CommandInputFilters the listEmpty query
CommandItem.valueSearchable option identityExplicit in this recipe
CommandItem.onSelectSave selection and closeApplication callback
CommandEmptyNo-match feedback—

#Accessibility

Name the selection control and search field. Keep the current selection visible in the trigger. Verify keyboard opening, list navigation, selection, dismissal, and focus return. Do not use a searchable popup for unrestricted free-text entry; these recipes select from predefined options. For server search, add loading and error states and disable client filtering when the server owns results.

Command filters each item's value and keywords. When a stable ID differs from its visible label, keep the ID as value and pass the label in keywords, as the Users and Timezones examples do. This lets users search names or GMT offsets while selection still returns the original ID.

The single-selection popup is a dialog containing the search field and listbox. Give PopoverContent an accessible name and retain PopoverTrigger's native aria-haspopup="dialog"; its aria-controls must reference the popup, rather than the nested listbox.

For multiple selection, use Combobox.Root with multiple and optionally control its array-valued value and onValueChange. Base UI manages listbox selection, the active descendant, filtering and focus; do not replace these with Command's single current-item state.

#Examples

#Users

This example additionally uses Avatar. Install it before copying the Code:

npx shadcn@latest add https://neobrutal-ui.andongmin.com/r/avatar.json
Loading preview...

#Timezones

Loading preview...

#Multiselect

Select more than one framework, move to an unselected option, and search without losing the existing selections. The trigger summarizes the selection; reopening the popup preserves it.

Loading preview...