import { Combobox, ComboboxContent, ComboboxItem, ComboboxList, ComboboxInput, ComboboxEmpty } from "@registry/components/ui/combobox";

export function ComboboxWithInputExample() {
  return (
    <div className="flex flex-wrap items-center gap-4">
      <Combobox items={fruits}>
        <ComboboxInput placeholder="Select fruits" className="w-56" />
        <ComboboxContent>
          <ComboboxList>
            {(item: (typeof fruits)[number]) => (
              <ComboboxItem key={item.value} value={item}>
                {item.label}
              </ComboboxItem>
            )}
          </ComboboxList>
          <ComboboxEmpty>No results found</ComboboxEmpty>
        </ComboboxContent>
      </Combobox>
    </div>
  );
}

Examples

With trigger

Use ComboboxTrigger for a button-style combobox without text input.

import { Combobox, ComboboxContent, ComboboxItem, ComboboxList, ComboboxTrigger, ComboboxValue } from "@registry/components/ui/combobox";

export function ComboboxExample() {
  return (
    <div className="flex flex-wrap items-center gap-4">
      <Combobox items={fruits}>
        <ComboboxTrigger className="w-56">
          <ComboboxValue placeholder="Select a fruit" />
        </ComboboxTrigger>
        <ComboboxContent>
          <ComboboxList>
            {(item: (typeof fruits)[number]) => (
              <ComboboxItem key={item.value} value={item.value}>
                {item.label}
              </ComboboxItem>
            )}
          </ComboboxList>
        </ComboboxContent>
      </Combobox>
    </div>
  );
}

With groups

Group related items using ComboboxGroup and ComboboxGroupLabel.

import { Combobox, ComboboxContent, ComboboxItem, ComboboxList, ComboboxTrigger, ComboboxValue, ComboboxGroup, ComboboxGroupLabel } from "@registry/components/ui/combobox";

export function ComboboxWithGroupsExample() {
  const frontend = [
    { value: "react", label: "React" },
    { value: "vue", label: "Vue" },
    { value: "svelte", label: "Svelte" },
  ];

  const backend = [
    { value: "next", label: "Next.js" },
    { value: "nuxt", label: "Nuxt" },
    { value: "astro", label: "Astro" },
  ];

  const allItems = [
    { group: "Frontend", items: frontend },
    { group: "Backend", items: backend },
  ];

  return (
    <div className="flex flex-wrap items-center gap-4">
      <Combobox items={allItems}>
        <ComboboxTrigger className="w-56">
          <ComboboxValue placeholder="Select a framework" />
        </ComboboxTrigger>
        <ComboboxContent>
          <ComboboxList>
            {(group: (typeof allItems)[number]) => (
              <ComboboxGroup key={group.group}>
                <ComboboxGroupLabel>{group.group}</ComboboxGroupLabel>
                {group.items.map((item) => (
                  <ComboboxItem key={item.value} value={item.value}>
                    {item.label}
                  </ComboboxItem>
                ))}
              </ComboboxGroup>
            )}
          </ComboboxList>
        </ComboboxContent>
      </Combobox>
    </div>
  );
}

Multi-select

Enable multiple selection and display selected values as chips inside the input.

import { Combobox, ComboboxContent, ComboboxItem, ComboboxList, ComboboxInput, ComboboxValue, ComboboxChip } from "@registry/components/ui/combobox";
import { useState } from "react";

export function ComboboxMultiSelectExample() {
  const [value, setValue] = useState<(typeof fruits)[number][]>([]);

  return (
    <div className="flex flex-col gap-3">
      <Combobox items={fruits} value={value} onValueChange={(v) => setValue(v ?? [])} multiple>
        <ComboboxInput
          className="w-56"
          placeholder={value.length > 0 ? "" : "Select fruits"}
          showRemove={false}
        >
          <ComboboxValue>
            {(selectedValue: (typeof fruits)[number][]) =>
              selectedValue.map((item) => (
                <ComboboxChip key={item.value}>{item.label}</ComboboxChip>
              ))
            }
          </ComboboxValue>
        </ComboboxInput>
        <ComboboxContent>
          <ComboboxList>
            {(item: (typeof fruits)[number]) => (
              <ComboboxItem key={item.value} value={item}>
                {item.label}
              </ComboboxItem>
            )}
          </ComboboxList>
        </ComboboxContent>
      </Combobox>
    </div>
  );
}

Controlled

Manage the selected value with React state.

import { Combobox, ComboboxContent, ComboboxItem, ComboboxList, ComboboxInput } from "@registry/components/ui/combobox";
import { useState } from "react";

export function ComboboxControlledExample() {
  const [value, setValue] = useState<(typeof fruits)[number] | null>(null);

  return (
    <div className="flex flex-col gap-3">
      <Combobox items={fruits} value={value} onValueChange={(v) => setValue(v)}>
        <ComboboxInput placeholder="Select a fruit" className="w-full" />
        <ComboboxContent>
          <ComboboxList>
            {(item: (typeof fruits)[number]) => (
              <ComboboxItem key={item.value} value={item}>
                {item.label}
              </ComboboxItem>
            )}
          </ComboboxList>
        </ComboboxContent>
      </Combobox>
      {value && <p className="text-sm text-gray-500">Selected: {value.label}</p>}
    </div>
  );
}

Installation

Copy the source code below into your project:

import * as React from "react";
import { Combobox as BaseCombobox, mergeProps, useRender } from "@base-ui/react";
import { cn } from "@registry/lib/utils";
import { CaretUpDownIcon, CheckIcon, XIcon } from "@phosphor-icons/react";
import { Tag } from "./tag";

const Combobox = BaseCombobox.Root;

function ComboboxValue({ ...props }: BaseCombobox.Value.Props) {
  return <BaseCombobox.Value data-slot="combobox-value" {...props} />;
}

function ComboboxContent({
  className,
  positionerProps,
  children,
  ...props
}: BaseCombobox.Popup.Props & {
  positionerProps?: BaseCombobox.Positioner.Props;
}) {
  return (
    <BaseCombobox.Portal>
      <BaseCombobox.Positioner
        className={cn(
          "z-10 min-w-(--anchor-width) outline-none select-none",
          positionerProps?.className,
        )}
        sideOffset={8}
        {...positionerProps}
      >
        <BaseCombobox.Popup
          className={cn(
            "group origin-(--transform-origin) rounded-xl bg-white p-1.5 text-gray-900 shadow-lg outline outline-gray-900/10 transition-[transform,scale,opacity] data-ending-style:scale-90 data-ending-style:opacity-0 data-starting-style:scale-90 data-starting-style:opacity-0 data-[side=none]:data-ending-style:transition-none data-[side=none]:data-starting-style:scale-100 data-[side=none]:data-starting-style:opacity-100 data-[side=none]:data-starting-style:transition-none dark:bg-gray-900 dark:text-gray-50 dark:shadow-none dark:-outline-offset-1 dark:outline-gray-700",
            className,
          )}
          data-slot="combobox-popup"
          {...props}
        >
          {children}
        </BaseCombobox.Popup>
      </BaseCombobox.Positioner>
    </BaseCombobox.Portal>
  );
}

function ComboboxList({ className, children, ...props }: BaseCombobox.List.Props) {
  return (
    <BaseCombobox.List
      className={cn(
        "relative grid max-h-(--available-height) scroll-py-6 grid-cols-[auto_auto_1fr_auto] overflow-y-auto empty:py-0",
        className,
      )}
      data-slot="combobox-list"
      {...props}
    >
      {children}
    </BaseCombobox.List>
  );
}

function ComboboxItemLeadingIcon({
  className,
  render,
  ...props
}: useRender.ComponentProps<"span">) {
  const iconElement = useRender({
    defaultTagName: "span",
    props: {
      ...mergeProps<"span">(
        {
          className: cn("mr-2 inline-flex size-4 shrink-0", className),
        },
        props,
      ),
      "data-slot": "combobox-item-leading-icon",
    },
    render,
  });
  return iconElement;
}

function ComboboxItemTrailingIcon({
  className,
  render,
  ...props
}: useRender.ComponentProps<"span">) {
  const iconElement = useRender({
    defaultTagName: "span",
    props: {
      ...mergeProps<"span">(
        {
          className: cn("ml-2 inline-flex size-4 shrink-0", className),
        },
        props,
      ),
      "data-slot": "combobox-item-trailing-icon",
    },
    render,
  });
  return iconElement;
}

function ComboboxItemLabel({ className, render, ...props }: useRender.ComponentProps<"span">) {
  const labelElement = useRender({
    defaultTagName: "span",
    props: {
      ...mergeProps<"span">(
        {
          className: cn("col-start-3 flex items-center", className),
        },
        props,
      ),
      "data-slot": "combobox-item-label",
    },
    render,
  });
  return labelElement;
}

function ComboboxItemIndicator({ className, ...props }: BaseCombobox.ItemIndicator.Props) {
  return (
    <ComboboxItemLeadingIcon>
      <BaseCombobox.ItemIndicator
        className={cn("size-4", className)}
        data-slot="combobox-item-indicator"
        render={<CheckIcon className="size-4" />}
        {...props}
      />
    </ComboboxItemLeadingIcon>
  );
}

function ComboboxItem({ className, children, ...props }: BaseCombobox.Item.Props) {
  return (
    <BaseCombobox.Item
      className={cn(
        "group col-span-full grid min-w-(--anchor-width) cursor-default grid-cols-subgrid items-center p-2 text-sm leading-4 outline-none select-none group-data-[side=none]:min-w-[calc(var(--anchor-width)+1rem)] group-data-[side=none]:pr-12 group-data-[side=none]:text-base group-data-[side=none]:leading-4 data-disabled:pointer-events-none data-disabled:cursor-not-allowed data-disabled:opacity-50 data-highlighted:relative data-highlighted:z-0 data-highlighted:text-gray-50 data-highlighted:before:absolute data-highlighted:before:inset-0 data-highlighted:before:z-[-1] data-highlighted:before:rounded-lg data-highlighted:before:bg-gray-900 dark:data-highlighted:text-gray-900 dark:data-highlighted:before:bg-gray-100 pointer-coarse:py-2.5 pointer-coarse:text-[0.925rem]",
        className,
      )}
      data-slot="combobox-item"
      {...props}
    >
      <ComboboxItemIndicator />
      {children}
    </BaseCombobox.Item>
  );
}

function ComboboxTrigger({ children, className, ...props }: BaseCombobox.Trigger.Props) {
  return (
    <BaseCombobox.Trigger
      className={cn(
        "flex min-h-9 min-w-36 cursor-default items-center justify-between gap-3 rounded-lg border border-gray-200 bg-white p-2 text-sm text-gray-900 transition-all duration-150 select-none hover:bg-gray-100 focus-visible:ring-[3px] focus-visible:ring-orange-500/30 focus-visible:outline focus-visible:-outline-offset-1 focus-visible:outline-orange-500 data-popup-open:bg-gray-100 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-50 dark:hover:bg-gray-800 dark:data-popup-open:bg-gray-800",
        className,
      )}
      data-slot="combobox-trigger"
      {...props}
    >
      {children}
      <CaretUpDownIcon className="size-4 shrink-0" />
    </BaseCombobox.Trigger>
  );
}

function ComboboxInput({
  className,
  children,
  showRemove = true,
  showTrigger = true,
  ...props
}: BaseCombobox.Input.Props & {
  children?: React.ReactNode;
  showTrigger?: boolean;
  showRemove?: boolean;
}) {
  return (
    <BaseCombobox.InputGroup
      className={cn(
        "flex min-h-9 items-center gap-1 rounded-lg border border-input p-2 text-sm text-foreground hover:border-accent",
        "focus-within:border-orange-500 focus-within:ring-[3px] focus-within:ring-orange-500/30",
        "bg-gray-950/5 dark:bg-gray-950/30",
        "transition-all duration-150",
        className,
      )}
      data-slot="combobox-input"
    >
      {children ? (
        <BaseCombobox.Chips className="flex flex-1 flex-wrap items-center gap-1">
          {children}
          <BaseCombobox.Input className="min-w-12 flex-1 leading-4 outline-none" {...props} />
        </BaseCombobox.Chips>
      ) : (
        <BaseCombobox.Input className="w-full leading-4 outline-none" {...props} />
      )}
      {showRemove && (
        <BaseCombobox.Clear className="pr-1">
          <XIcon className="size-4" />
        </BaseCombobox.Clear>
      )}
      {showTrigger && (
        <BaseCombobox.Trigger>
          <CaretUpDownIcon className="size-4" />
        </BaseCombobox.Trigger>
      )}
    </BaseCombobox.InputGroup>
  );
}

function ComboboxEmpty({ className, children, ...props }: BaseCombobox.Empty.Props) {
  return (
    <BaseCombobox.Empty
      className={cn("px-2.5 py-2 text-sm text-gray-600 empty:hidden dark:text-gray-400", className)}
      data-slot="combobox-empty"
      {...props}
    >
      {children}
    </BaseCombobox.Empty>
  );
}

function ComboboxSeparator(props: BaseCombobox.Separator.Props) {
  return (
    <BaseCombobox.Separator
      className="h-px w-full bg-gray-200 dark:bg-gray-800"
      data-slot="combobox-separator"
      {...props}
    />
  );
}

function ComboboxChips({ className, ...props }: BaseCombobox.Chips.Props) {
  return (
    <BaseCombobox.Chips
      className={cn("flex flex-wrap gap-1", className)}
      data-slot="combobox-chips"
      {...props}
    />
  );
}

function ComboboxChip({
  className,
  children,
  showRemove = true,
  ...props
}: BaseCombobox.Chip.Props & { showRemove?: boolean }) {
  return (
    <BaseCombobox.Chip
      render={<Tag variant="outline" size="sm" className="rounded-sm" />}
      className={cn("h-4.5", className)}
      data-slot="combobox-chip"
      {...props}
    >
      {children}
      {showRemove && (
        <BaseCombobox.ChipRemove>
          <XIcon className="size-3" />
        </BaseCombobox.ChipRemove>
      )}
    </BaseCombobox.Chip>
  );
}

function ComboboxGroup({ className, ...props }: BaseCombobox.Group.Props) {
  return (
    <BaseCombobox.Group
      className={cn("col-span-full grid grid-cols-subgrid", className)}
      data-slot="combobox-group"
      {...props}
    />
  );
}

function ComboboxGroupLabel({ className, ...props }: BaseCombobox.GroupLabel.Props) {
  return (
    <BaseCombobox.GroupLabel
      className={cn("col-start-2 py-1.5 text-sm text-muted-foreground select-none", className)}
      data-slot="combobox-group-label"
      {...props}
    />
  );
}

export {
  Combobox,
  ComboboxTrigger,
  ComboboxContent,
  ComboboxList,
  ComboboxItem,
  ComboboxValue,
  ComboboxInput,
  ComboboxEmpty,
  ComboboxSeparator,
  ComboboxChips,
  ComboboxChip,
  ComboboxItemIndicator,
  ComboboxGroup,
  ComboboxGroupLabel,
  ComboboxItemLeadingIcon,
  ComboboxItemTrailingIcon,
  ComboboxItemLabel,
};

API Reference

Combobox

This component does not add any props on top of Base UI Combobox.Root . See the Base UI docs for the full API reference.

ComboboxTrigger

This component does not add any props on top of Base UI Combobox.Trigger . See the Base UI docs for the full API reference.

ComboboxContent

The ComboboxContent component extends the Base UI Combobox.Popup props and adds the following:

PropTypeDefaultDescription
positionerPropsBaseCombobox.Positioner.PropsProps forwarded to the underlying Positioner component. Includes align, side, sideOffset, alignOffset, collisionPadding, etc.

ComboboxList

This component does not add any props on top of Base UI Combobox.List . See the Base UI docs for the full API reference.

ComboboxItem

This component does not add any props on top of Base UI Combobox.Item . See the Base UI docs for the full API reference.

ComboboxValue

This component does not add any props on top of Base UI Combobox.Value . See the Base UI docs for the full API reference.

ComboboxInput

The ComboboxInput component extends the Base UI Combobox.Input props and adds the following:

PropTypeDefaultDescription
showRemovebooleantrueWhether to show the clear button. Hidden automatically when children are provided.
showTriggerbooleantrueWhether to show the dropdown trigger button.

ComboboxEmpty

This component does not add any props on top of Base UI Combobox.Empty . See the Base UI docs for the full API reference.

ComboboxSeparator

This component does not add any props on top of Base UI Combobox.Separator . See the Base UI docs for the full API reference.

ComboboxChips

This component does not add any props on top of Base UI Combobox.Chips . See the Base UI docs for the full API reference.

ComboboxChip

The ComboboxChip component extends the Base UI Combobox.Chip props and adds the following:

PropTypeDefaultDescription
showRemovebooleantrueWhether to show the remove button on the chip.

ComboboxItemIndicator

This component does not add any props on top of Base UI Combobox.ItemIndicator . See the Base UI docs for the full API reference.

ComboboxGroup

This component does not add any props on top of Base UI Combobox.Group . See the Base UI docs for the full API reference.

ComboboxGroupLabel

This component does not add any props on top of Base UI Combobox.GroupLabel . See the Base UI docs for the full API reference.