1
ComponentsForm Controls

Combobox

Combobox provides searchable single- and multi-select lists, including chip-based selection.

Purpose

Combobox provides searchable selection constrained to listed options. Use it for single select or chip-based multi-select. Use Select for a short unsearchable list and Autocomplete when free text is a valid value.

Searchable select

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

export function Example() {
  return (
    <Combobox>
      <ComboboxInput aria-label="Team" placeholder="Select team" />
      <ComboboxPopup><ComboboxList><ComboboxItem value="design">Design</ComboboxItem></ComboboxList></ComboboxPopup>
    </Combobox>
  );
}

Installation

With the Siteplane registry alias configured, add the component and import the installed source from your app.

CLI

pnpm dlx shadcn@4.16.1 add @siteplane/combobox

Import

import { Combobox, ComboboxChip, ComboboxChipRemove, ComboboxChips, ComboboxChipsInput, ComboboxEmpty, ComboboxGroup, ComboboxGroupLabel, ComboboxInput, ComboboxItem, ComboboxList, ComboboxPopup, ComboboxRow, ComboboxStatus, ComboboxTrigger, ComboboxValue, useComboboxFilter } from "@/components/ui/combobox";

Need the machine-readable source metadata? View registry JSON.

Usage

Use Combobox for searchable known options. Keep the selected value and popup items in the same primitive tree.

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

export function Example() {
  return (
    <Combobox>
      <ComboboxInput aria-label="Team" placeholder="Select team" />
      <ComboboxPopup><ComboboxList><ComboboxItem value="design">Design</ComboboxItem></ComboboxList></ComboboxPopup>
    </Combobox>
  );
}

Examples

Chips, clear buttons, grouped options, field integration, empty states, input addons and control states are props and compositions of the same primitive.

Multi select with chips

<Combobox defaultValue={["Frontend", "Docs"]} items={tags} multiple>
  <ComboboxChips aria-label="Tags">
    <ComboboxValue>
      {(value: string[]) =>
        value.map((tag) => (
          <ComboboxChip aria-label={tag} key={tag}>{tag}</ComboboxChip>
        ))
      }
    </ComboboxValue>
    <ComboboxChipsInput placeholder="Add tags" />
  </ComboboxChips>
  <ComboboxPopup>
    <ComboboxEmpty>No tag found.</ComboboxEmpty>
    <ComboboxList>
      {(tag: string) => <ComboboxItem key={tag} value={tag}>{tag}</ComboboxItem>}
    </ComboboxList>
  </ComboboxPopup>
</Combobox>

With clear button

<Combobox defaultValue="Next.js" items={frameworks}>
  <ComboboxInput aria-label="Framework" placeholder="Select framework" showClear />
  <ComboboxPopup>
    <ComboboxEmpty>No framework found.</ComboboxEmpty>
    <ComboboxList>
      {(framework: string) => (
        <ComboboxItem key={framework} value={framework}>{framework}</ComboboxItem>
      )}
    </ComboboxList>
  </ComboboxPopup>
</Combobox>

Grouped options

const filter = useComboboxFilter();
const [query, setQuery] = useState("");

const filteredGroups = useMemo(() => {
  return technologyGroups
    .map((group) => ({
      label: group.label,
      items: group.items.filter((item) => filter.contains(item, query)),
    }))
    .filter((group) => group.items.length > 0);
}, [filter, query]);

<Combobox
  filter={null}
  items={technologyGroups.flatMap((group) => group.items)}
  onInputValueChange={setQuery}
>
  <ComboboxInput aria-label="Technology" placeholder="Select technology" />
  <ComboboxPopup>
    <ComboboxEmpty>No technology found.</ComboboxEmpty>
    <ComboboxList>
      {filteredGroups.map((group) => (
        <Fragment key={group.label}>
          <ComboboxGroup>
            <ComboboxGroupLabel>{group.label}</ComboboxGroupLabel>
            {group.items.map((item) => (
              <ComboboxItem key={item} value={item}>{item}</ComboboxItem>
            ))}
          </ComboboxGroup>
          <ComboboxSeparator />
        </Fragment>
      ))}
    </ComboboxList>
  </ComboboxPopup>
</Combobox>

Empty state

<Combobox items={frameworks}>
  <ComboboxInput aria-label="Framework" placeholder="Search frameworks" />
  <ComboboxPopup>
    <ComboboxEmpty>No matching option found.</ComboboxEmpty>
    <ComboboxList>
      {(framework: string) => (
        <ComboboxItem key={framework} value={framework}>{framework}</ComboboxItem>
      )}
    </ComboboxList>
  </ComboboxPopup>
</Combobox>

Input addons and sizes

{/* Start addon */}
<ComboboxInput aria-label="Assignee" placeholder="Select assignee" startAddon={<UserIcon />} />

{/* Sizes */}
<ComboboxInput size="sm" ... />
<ComboboxInput ... />
<ComboboxInput size="lg" ... />
<ComboboxInput size="xl" ... />

Field integration and error

Typing filters the list.

Assignee: not submitted
<Form onSubmit={onSubmit}>
  <Field name="assignee">
    <FieldLabel>Assignee</FieldLabel>
    <Combobox items={assignees} name="assignee" required>
      <ComboboxInput placeholder="Select teammate" />
      <ComboboxPopup>
        <ComboboxEmpty>No teammate found.</ComboboxEmpty>
        <ComboboxList>
          {(person: string) => (
            <ComboboxItem key={person} value={person}>{person}</ComboboxItem>
          )}
        </ComboboxList>
      </ComboboxPopup>
    </Combobox>
    <FieldDescription>Typing filters the list.</FieldDescription>
    <FieldError>Please select a teammate.</FieldError>
  </Field>
  <Button loading={loading} type="submit">Submit</Button>
</Form>

States

{/* Disabled */}
<Combobox items={frameworks}>
  <ComboboxInput aria-label="Framework" disabled placeholder="Select framework" />
  <ComboboxPopup>
    <ComboboxEmpty>No framework found.</ComboboxEmpty>
    <ComboboxList>
      {(framework: string) => (
        <ComboboxItem key={framework} value={framework}>{framework}</ComboboxItem>
      )}
    </ComboboxList>
  </ComboboxPopup>
</Combobox>

{/* Invalid */}
<Combobox items={frameworks}>
  <ComboboxInput aria-invalid aria-label="Framework" placeholder="Select framework" />
  <ComboboxPopup>
    <ComboboxEmpty>No framework found.</ComboboxEmpty>
    <ComboboxList>
      {(framework: string) => (
        <ComboboxItem key={framework} value={framework}>{framework}</ComboboxItem>
      )}
    </ComboboxList>
  </ComboboxPopup>
</Combobox>

API Reference

The reference lists the source-owned exports and props that are easy to miss in visual examples. Inherited Base UI props remain available unless the wrapper narrows them.

  • Filter through root items or useComboboxFilter; do not render a searchable list without filtering it.
  • ComboboxTrigger supports a button-triggered composition where the selected value stays in the trigger and search lives in the popup.
  • Set ComboboxInput showTrigger={false} for a plain search field without the trailing dropdown affordance.
  • ComboboxPopup accepts side, align, sideOffset, and alignOffset; defaults are bottom and start alignment.
  • ComboboxItem disabled marks an unavailable option.
  • ComboboxList provides the bounded ScrollArea and fades for long lists; ComboboxRow supports grid layouts.
  • ComboboxStatus renders loading or result status.
  • ComboboxChips startAddon adds leading context. Set size on ComboboxChipsInput to scale the complete chips field.

Motion

Component motion uses the shared Siteplane motion contract. See the global motion guide for provider setup, tokens and reduced-motion behavior.

  • Popup, filter, highlight, chips, and reduced-motion behavior remain native.
  • Active options and icon actions use a pointer cursor; input text keeps a text cursor.
  • Results and status changes must not change the input dimensions.

Accessibility

  • Give the input or chips field a visible label, aria-label, or aria-labelledby.
  • Each chip needs an accessible name; its remove control already supplies the removal action semantics.
  • Keep disabled options unavailable to pointer and keyboard selection.
  • Connect invalid state to the input and render a visible FieldError.

Implementation Guidance

  • Use Combobox for searchable constrained selection, not free-text search.
  • Keep one filtering source and render input or trigger, popup, list, and items as one composition.
  • Preserve native cursor, focus, disabled, popup, scroll, and motion behavior.

On This Page