Combobox
A searchable select built from cmdk and Radix Popover. Compose it from eight primitive parts — trigger, content, input, list, item, empty, and group — for full control over layout and behavior.
- ✓Built on cmdk + Radix UI Popover — no extra library required
- ✓Fully composable: eight named sub-components
- ✓Single and multi-select patterns via the selected prop on ComboboxItem
- ✓Searchable with built-in keyboard navigation
- ✓Group items with ComboboxGroup and a heading label
- ✓Animates open/close with Tailwind data-state variants
import { Combobox }from "@aetherstack/ui"
combobox.tsx
"use client"
import { useState } from "react"
import { ChevronsUpDown } from "lucide-react"
import {
Combobox, ComboboxTrigger, ComboboxContent,
ComboboxInput, ComboboxList, ComboboxItem, ComboboxEmpty,
} from "@/components/ui/combobox"
import { Button } from "@/components/ui/button"
const fruits = ["Apple", "Banana", "Cherry", "Date", "Elderberry"]
export function ComboboxDemo() {
const [open, setOpen] = useState(false)
const [value, setValue] = useState("")
return (
<Combobox open={open} onOpenChange={setOpen}>
<ComboboxTrigger asChild>
<Button variant="outline" className="w-48 justify-between">
{value || "Select a fruit…"}
<ChevronsUpDown className="opacity-50" />
</Button>
</ComboboxTrigger>
<ComboboxContent>
<ComboboxInput placeholder="Search fruits…" />
<ComboboxList>
<ComboboxEmpty>No fruit found.</ComboboxEmpty>
{fruits.map((fruit) => (
<ComboboxItem
key={fruit}
value={fruit}
selected={value === fruit}
onSelect={() => {
setValue(fruit === value ? "" : fruit)
setOpen(false)
}}
>
{fruit}
</ComboboxItem>
))}
</ComboboxList>
</ComboboxContent>
</Combobox>
)
}Usage
Basic select
A single-select combobox with live filtering.
With groups
Use ComboboxGroup with a heading to visually separate items into categories.
Multi-select
Keep the popover open on select and track an array of selected values.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| Combobox | PopoverPrimitive.Root | — | Root wrapper — manages open/closed state. Accepts open, onOpenChange, defaultOpen. |
| ComboboxTrigger | PopoverPrimitive.Trigger | — | The element that toggles the combobox open. Use asChild to render as a Button. |
| ComboboxContent | React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content> | — | Popover panel containing the Command root. Renders in a portal. |
| ComboboxInput | React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input> | — | The search input — filters the list in real time. |
| ComboboxList | React.ComponentPropsWithoutRef<typeof CommandPrimitive.List> | — | Scrollable list container. Max height 300px by default. |
| ComboboxItem | React.ComponentPropsWithoutRef<typeof CommandPrimitive.Item> & { selected?: boolean } | — | A single selectable option. Set selected={true} to show a checkmark. |
| ComboboxEmpty | React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty> | — | Rendered when no items match the current search input. |
| ComboboxGroup | React.ComponentPropsWithoutRef<typeof CommandPrimitive.Group> | — | Groups items under a labeled heading. Pass heading prop for the group label. |
* Required props
Accessibility
- →Built on cmdk which implements the ARIA combobox pattern with listbox role.
- →Keyboard navigation: arrow keys move between items, Enter selects, Escape closes.
- →Search input is automatically focused when the popover opens.
- →Selected state is communicated via aria-selected on each item.
- →Portal rendering ensures the listbox is not clipped by overflow-hidden containers.