The Combobox isn’t a standalone registry item — it’s a composition of the popover and command components, same as upstream shadcn. Install both, then wire the selection state yourself.
Installation
npx logic2b@latest add popover command
Usage
import * as React from "react"
import { Check, ChevronsUpDown } from "lucide-react"
import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from "@/components/ui/command"
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"
const frameworks = [
{ value: "next", label: "Next.js" },
{ value: "vite", label: "Vite" },
]
export function Combobox() {
const [open, setOpen] = React.useState(false)
const [value, setValue] = React.useState("")
return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button variant="outline" role="combobox" aria-expanded={open}>
{value ? frameworks.find((f) => f.value === value)?.label : "Select..."}
<ChevronsUpDown className="ml-2 size-4 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-[220px] p-0">
<Command>
<CommandInput placeholder="Search..." />
<CommandList>
<CommandEmpty>No results.</CommandEmpty>
<CommandGroup>
{frameworks.map((f) => (
<CommandItem
key={f.value}
value={f.value}
onSelect={(v) => {
setValue(v === value ? "" : v)
setOpen(false)
}}
>
<Check className={cn("mr-2 size-4", value === f.value ? "opacity-100" : "opacity-0")} />
{f.label}
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
)
}
Accessibility
- The trigger sets
role="combobox"andaria-expanded, so assistive tech announces the collapsed/expanded state. - Command handles list navigation: ↑/↓ move between items, Enter selects, and typing filters.
- Esc closes the popover and returns focus to the trigger.