Components
Base Components
Combobox
Autocomplete input with a list of suggestions.
Example
Installation
The Combobox uses the Popover component. Add bothpopover.tsx and combobox.tsx to your project.
Install the following dependencies:
npm install radix-uiCreate a combobox.tsx file and paste the following code into it.
"use client";import * as React from "react";import { Combobox as ComboboxPrimitive } from "@base-ui/react";import { CheckIcon, ChevronDownIcon, XIcon } from "lucide-react";import { cn } from "@/lib/utils";import { Button } from "@/components/ui/button";import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput,} from "@/components/ui/input-group";const Combobox = ComboboxPrimitive.Root;function ComboboxValue({ ...props }: ComboboxPrimitive.Value.Props) { return <ComboboxPrimitive.Value data-slot="combobox-value" {...props} />;}function ComboboxTrigger({ className, children, ...props}: ComboboxPrimitive.Trigger.Props) { return ( <ComboboxPrimitive.Trigger data-slot="combobox-trigger" className={cn("[&_svg:not([class*='size-'])]:size-4", className)} {...props} > {children} <ChevronDownIcon className="pointer-events-none size-4 text-muted-foreground" /> </ComboboxPrimitive.Trigger> );}function ComboboxClear({ className, ...props }: ComboboxPrimitive.Clear.Props) { return ( <ComboboxPrimitive.Clear data-slot="combobox-clear" className={cn(className)} {...props} render={ <InputGroupButton variant="ghost" size="icon-xs"> <XIcon className="pointer-events-none" /> </InputGroupButton> } /> );}function ComboboxInput({ className, children, disabled = false, showTrigger = true, showClear = false, ...props}: ComboboxPrimitive.Input.Props & { showTrigger?: boolean; showClear?: boolean;}) { return ( <InputGroup className={cn("w-auto", className)}> <ComboboxPrimitive.Input render={<InputGroupInput disabled={disabled} />} {...props} /> <InputGroupAddon align="inline-end"> {showTrigger && ( <ComboboxTrigger render={ <InputGroupButton size="icon-xs" variant="ghost" data-slot="input-group-button" className="group-has-data-[slot=combobox-clear]/input-group:hidden data-pressed:bg-transparent" disabled={disabled} /> } /> )} {showClear && <ComboboxClear disabled={disabled} />} </InputGroupAddon> {children} </InputGroup> );}function ComboboxContent({ className, side = "bottom", sideOffset = 6, align = "start", alignOffset = 0, anchor, ...props}: ComboboxPrimitive.Popup.Props & Pick< ComboboxPrimitive.Positioner.Props, "side" | "align" | "sideOffset" | "alignOffset" | "anchor" >) { return ( <ComboboxPrimitive.Portal> <ComboboxPrimitive.Positioner side={side} sideOffset={sideOffset} align={align} alignOffset={alignOffset} anchor={anchor} className="isolate z-50" > <ComboboxPrimitive.Popup data-slot="combobox-content" data-chips={!!anchor} className={cn( "group/combobox-content relative max-h-(--available-height) w-(--anchor-width) max-w-(--available-width) min-w-[calc(var(--anchor-width)+--spacing(7))] origin-(--transform-origin) overflow-hidden rounded-lg bg-popover text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-[chips=true]:min-w-(--anchor-width) data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 *:data-[slot=input-group]:m-1 *:data-[slot=input-group]:mb-0 *:data-[slot=input-group]:h-8 *:data-[slot=input-group]:border-input/30 *:data-[slot=input-group]:bg-input/30 *:data-[slot=input-group]:shadow-none data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95", className, )} {...props} /> </ComboboxPrimitive.Positioner> </ComboboxPrimitive.Portal> );}function ComboboxList({ className, ...props }: ComboboxPrimitive.List.Props) { return ( <ComboboxPrimitive.List data-slot="combobox-list" className={cn( "no-scrollbar max-h-[min(calc(--spacing(72)---spacing(9)),calc(var(--available-height)---spacing(9)))] scroll-py-1 overflow-y-auto overscroll-contain p-1 data-empty:p-0", className, )} {...props} /> );}function ComboboxItem({ className, children, ...props}: ComboboxPrimitive.Item.Props) { return ( <ComboboxPrimitive.Item data-slot="combobox-item" className={cn( "relative flex w-full cursor-default items-center gap-2 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden select-none data-highlighted:bg-accent data-highlighted:text-accent-foreground not-data-[variant=destructive]:data-highlighted:**:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", className, )} {...props} > {children} <ComboboxPrimitive.ItemIndicator render={ <span className="pointer-events-none absolute right-2 flex size-4 items-center justify-center"> <CheckIcon className="pointer-events-none" /> </span> } /> </ComboboxPrimitive.Item> );}function ComboboxGroup({ className, ...props }: ComboboxPrimitive.Group.Props) { return ( <ComboboxPrimitive.Group data-slot="combobox-group" className={cn(className)} {...props} /> );}function ComboboxLabel({ className, ...props}: ComboboxPrimitive.GroupLabel.Props) { return ( <ComboboxPrimitive.GroupLabel data-slot="combobox-label" className={cn("px-2 py-1.5 text-xs text-muted-foreground", className)} {...props} /> );}function ComboboxCollection({ ...props }: ComboboxPrimitive.Collection.Props) { return ( <ComboboxPrimitive.Collection data-slot="combobox-collection" {...props} /> );}function ComboboxEmpty({ className, ...props }: ComboboxPrimitive.Empty.Props) { return ( <ComboboxPrimitive.Empty data-slot="combobox-empty" className={cn( "hidden w-full justify-center py-2 text-center text-sm text-muted-foreground group-data-empty/combobox-content:flex", className, )} {...props} /> );}function ComboboxSeparator({ className, ...props}: ComboboxPrimitive.Separator.Props) { return ( <ComboboxPrimitive.Separator data-slot="combobox-separator" className={cn("-mx-1 my-1 h-px bg-border", className)} {...props} /> );}function ComboboxChips({ className, ...props}: React.ComponentPropsWithRef<typeof ComboboxPrimitive.Chips> & ComboboxPrimitive.Chips.Props) { return ( <ComboboxPrimitive.Chips data-slot="combobox-chips" className={cn( "flex min-h-8 flex-wrap items-center gap-1 rounded-lg border border-input bg-transparent bg-clip-padding px-2.5 py-1 text-sm transition-colors focus-within:border-ring focus-within:ring-3 focus-within:ring-ring/50 has-aria-invalid:border-destructive has-aria-invalid:ring-3 has-aria-invalid:ring-destructive/20 has-data-[slot=combobox-chip]:px-1 dark:bg-input/30 dark:has-aria-invalid:border-destructive/50 dark:has-aria-invalid:ring-destructive/40", className, )} {...props} /> );}function ComboboxChip({ className, children, showRemove = true, ...props}: ComboboxPrimitive.Chip.Props & { showRemove?: boolean;}) { return ( <ComboboxPrimitive.Chip data-slot="combobox-chip" className={cn( "flex h-[calc(--spacing(5.25))] w-fit items-center justify-center gap-1 rounded-sm bg-muted px-1.5 text-xs font-medium whitespace-nowrap text-foreground has-disabled:pointer-events-none has-disabled:cursor-not-allowed has-disabled:opacity-50 has-data-[slot=combobox-chip-remove]:pr-0", className, )} {...props} > {children} {showRemove && ( <ComboboxPrimitive.ChipRemove className="-ml-1 opacity-50 hover:opacity-100" data-slot="combobox-chip-remove" render={ <Button variant="ghost" size="sm"> <XIcon className="pointer-events-none" /> </Button> } /> )} </ComboboxPrimitive.Chip> );}function ComboboxChipsInput({ className, ...props}: ComboboxPrimitive.Input.Props) { return ( <ComboboxPrimitive.Input data-slot="combobox-chip-input" className={cn("min-w-16 flex-1 outline-none", className)} {...props} /> );}function useComboboxAnchor() { return React.useRef<HTMLDivElement | null>(null);}export { Combobox, ComboboxInput, ComboboxContent, ComboboxList, ComboboxItem, ComboboxGroup, ComboboxLabel, ComboboxCollection, ComboboxEmpty, ComboboxSeparator, ComboboxChips, ComboboxChip, ComboboxChipsInput, ComboboxTrigger, ComboboxValue, useComboboxAnchor,};Check the import paths to ensure they match your project setup.
Usage
import { Combobox, ComboboxInput } from "@/components/ui/combobox";const frameworks = ["Next.js", "SvelteKit", "Nuxt.js", "Remix", "Astro"];
export function ExampleCombobox() {
return (
<Combobox items={frameworks}>
<ComboboxInput placeholder="Select a framework" />
<ComboboxContent>
<ComboboxEmpty>No items found.</ComboboxEmpty>
<ComboboxList>
{(item) => (
<ComboboxItem key={item} value={item}>
{item}
</ComboboxItem>
)}
</ComboboxList>
</ComboboxContent>
</Combobox>
);
}Basic
A simple combobox with a list of frameworks.Multiple
A combobox with multiple selection usingmultiple and chips.
Next.js
Astro
Clear Button
Use theshowClear prop to show a clear button.
Groups
UseComboboxGroup and ComboboxSeparator to group items.
Custom Items
You can render a custom layout insideComboboxItem.
Invalid
Usearia-invalid on ComboboxInput to indicate an invalid state.
Disabled
Use thedisabled prop to disable the combobox.
Auto Highlight
UseautoHighlight to automatically highlight the first item while filtering.
Popup
Trigger the combobox from a button and placeComboboxInput inside the popup.
Input Group
You can add an addon withInputGroupAddon inside ComboboxInput.
RTL
Usedir="rtl" to support right-to-left layouts.
API Reference
Combobox
| Prop | Type | Description |
|---|---|---|
items | string[] or ComboboxOption[] | Options to display. |
value | string | Controlled selected value. |
onValueChange | (value: string) => void | Called when selection changes. |
placeholder | string | Placeholder when nothing selected. |
emptyText | string | Text when filter has no results. |
itemToStringValue | (item) => string | Custom label for object items. |
disabled | boolean | Disable the trigger. |
children | ReactNode | Custom trigger (default: Button). |
ComboboxInput
Same as Combobox but uses an Input as the trigger and filters as the user types. Useplaceholder for the input placeholder.