Components

Base Components

Combobox

Autocomplete input with a list of suggestions.

Example

Installation

The Combobox uses the Popover component. Add both popover.tsx and combobox.tsx to your project.

Install the following dependencies:

npm install radix-ui

Create 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 using multiple and chips.

Clear Button

Use the showClear prop to show a clear button.

Groups

Use ComboboxGroup and ComboboxSeparator to group items.

Custom Items

You can render a custom layout inside ComboboxItem.

Invalid

Use aria-invalid on ComboboxInput to indicate an invalid state.

Disabled

Use the disabled prop to disable the combobox.

Auto Highlight

Use autoHighlight to automatically highlight the first item while filtering.
Trigger the combobox from a button and place ComboboxInput inside the popup.

Input Group

You can add an addon with InputGroupAddon inside ComboboxInput.

RTL

Use dir="rtl" to support right-to-left layouts.

API Reference

Combobox

PropTypeDescription
itemsstring[] or ComboboxOption[]Options to display.
valuestringControlled selected value.
onValueChange(value: string) => voidCalled when selection changes.
placeholderstringPlaceholder when nothing selected.
emptyTextstringText when filter has no results.
itemToStringValue(item) => stringCustom label for object items.
disabledbooleanDisable the trigger.
childrenReactNodeCustom trigger (default: Button).

ComboboxInput

Same as Combobox but uses an Input as the trigger and filters as the user types. Use placeholder for the input placeholder.

On this page