Skip to content

Combobox

A searchable select for long lists, with single or multiple values as chips.

Status
Beta
Category
Selection
Adoption
Not used yet
import { Combobox } from "@oration/canon/components/combobox";
packages/canon/src/components/combobox.tsx

INV-20418

Northwind Freight, $18,240.00, due October 12

import {  Combobox,  ComboboxContent,  ComboboxEmpty,  ComboboxInput,  ComboboxItem,  ComboboxList,} from "@oration/canon/components/combobox";import { Label } from "@oration/canon/components/label";import { toast } from "@oration/canon/components/toast";import * as React from "react";export function Hero() {    type Person = { id: string; name: string; team: string };    const people: Person[] = [        { id: "maya", name: "Maya Okafor", team: "Revenue" },        { id: "priya", name: "Priya Raman", team: "Accounts payable" },        { id: "tomas", name: "Tomás Ferreira", team: "Treasury" },        { id: "jordan", name: "Jordan Lee", team: "Procurement" },        { id: "aisha", name: "Aisha Bello", team: "Accounts payable" },        { id: "wen", name: "Wen Zhou", team: "Controller" },    ];    const id = React.useId();    const [approver, setApprover] = React.useState<Person | null>(null);    return (        <div className="flex w-full max-w-sm flex-col gap-4 rounded-xl bg-card p-4 text-left shadow-border">            <div className="flex flex-col gap-0.5">                <p className="text-sm font-medium">INV-20418</p>                <p className="text-13 text-muted-foreground">                    Northwind Freight,{" "}                    <span className="tabular-nums">$18,240.00</span>, due                    October 12                </p>            </div>            <div className="flex flex-col gap-1.5">                <Label htmlFor={id}>Approver</Label>                <Combobox                    items={people}                    value={approver}                    itemToStringLabel={(person: Person) => person.name}                    onValueChange={(person: Person | null) => {                        setApprover(person);                        if (person) {                            toast.add({                                type: "success",                                title: `Approval requested from ${person.name}`,                                description:                                    "They get an email and a task in their queue.",                            });                        }                    }}                >                    <ComboboxInput                        id={id}                        placeholder="Search people"                        className="w-full"                    />                    <ComboboxContent>                        <ComboboxEmpty>                            No one on the team by that name.                        </ComboboxEmpty>                        <ComboboxList>                            {(person: Person) => (                                <ComboboxItem key={person.id} value={person}>                                    <span className="flex-1 truncate">                                        {person.name}                                    </span>                                    <span className="text-xs text-muted-foreground">                                        {person.team}                                    </span>                                </ComboboxItem>                            )}                        </ComboboxList>                    </ComboboxContent>                </Combobox>            </div>        </div>    );}

Usage#

Combobox is a text field that filters a list as you type, built on Base UI Combobox. It is the picker for lists that are long, grow with the workspace or are easier to type than to scan: people, suppliers, GL accounts, invoices. With multiple it holds several values as removable chips in the field. The common mistake is reaching for it on a short fixed list, where a search box is only friction; under about 15 options, use a select.

When to use

  • To pick one record from a list people search by name: an approver, a supplier, a GL account.
  • For several values from a searchable list, shown as chips in the field: watchers, approvers, email domains.
  • As an add control above a list, where each pick adds a row and the field clears for the next one.
  • When the list is long enough that scanning it is slower than typing three letters, about 15 options or more.

When not to use

  • For a short fixed list such as payment terms or remittance format. Use Select field
  • For a short list whose rows need icons, tags or groups, without search. Use Select
  • For a time zone. It already searches by city, zone and offset. Use Timezone select
  • For free-form values people type rather than choose, such as email domains that aren't in any list. Use Tag input
  • For jumping to a page or running a command. Use Command menu

Every field has a label

Link a visible Label to the input's id, or give the input an aria-label when it sits above the list it adds to. The placeholder says what to type, never what the field is.

Selects match inputs

The field is an Input group: 32px, 10px corners, the Field Stroke, and the same indigo focus ring as every input, so a combobox sits in a form column without a seam.

Anatomy#

Hal
Suppliers
Halcyon
Halvorsen Logistics
  1. Input. ComboboxInput: an Input group with the text input, 32px with 10px corners. className sizes the group.
  2. Trigger. A 24px ghost icon button with the chevron that opens the list. With showClear, a clear button replaces it once there is a value.
  3. Popup. Popover White, 10px corners, the overlay shadow and a 1px ink ring at 10%. At least the field's width plus 28px, 6px below it, scrolling at 18rem.
  4. Group label. ComboboxLabel: 12px Slate Meta over a group of rows.
  5. Item. ComboboxItem: 14px text with 8px corners and room for the check. The highlighted row fills with the accent color.
  6. Indicator. A 16px check, 8px from the right, on the chosen rows.
  7. Chips. With multiple: ComboboxChips replaces the input group, wraps chips and a ComboboxChipsInput, and grows from 32px.
  8. Chip. ComboboxChip: 21px, 12px medium text on Well Gray with 6px corners and a remove button.

Examples#

Search a long list

Pass items and a function child to ComboboxList; typing filters the rows. ComboboxEmpty says what to do when nothing matches.

import {  Combobox,  ComboboxContent,  ComboboxEmpty,  ComboboxInput,  ComboboxItem,  ComboboxList,} from "@oration/canon/components/combobox";import { Label } from "@oration/canon/components/label";import { toast } from "@oration/canon/components/toast";import * as React from "react";export function Basic() {    const suppliers = [        "Brightline Freight",        "Cobalt Packaging",        "Delmar Office Supply",        "Evergreen Janitorial",        "Fairway Print Co.",        "Granite Ridge Staffing",        "Halcyon",        "Halvorsen Logistics",        "Keystone Software",        "Lakeside Utilities",        "Meridian Legal",        "Northwind Freight",        "Orchard Street",        "Pinecrest Catering",        "Quarry Hill Security",        "Redwood Insurance",    ];    const id = React.useId();    const [supplier, setSupplier] = React.useState<string | null>(null);    return (        <div className="flex w-full max-w-xs flex-col gap-1.5 text-left">            <Label htmlFor={id}>Supplier</Label>            <Combobox                items={suppliers}                value={supplier}                onValueChange={(next: string | null) => {                    setSupplier(next);                    if (next)                        toast.add({ title: "Supplier set", description: next });                }}            >                <ComboboxInput                    id={id}                    placeholder="Search suppliers"                    className="w-full"                />                <ComboboxContent>                    <ComboboxEmpty>                        No suppliers match. Check the spelling or add one.                    </ComboboxEmpty>                    <ComboboxList>                        {(name: string) => (                            <ComboboxItem key={name} value={name}>                                {name}                            </ComboboxItem>                        )}                    </ComboboxList>                </ComboboxContent>            </Combobox>        </div>    );}

Clearable

showClear swaps the chevron for a clear button once there is a value, and the root reports null.

import {  Combobox,  ComboboxContent,  ComboboxEmpty,  ComboboxInput,  ComboboxItem,  ComboboxList,} from "@oration/canon/components/combobox";import { Label } from "@oration/canon/components/label";import { toast } from "@oration/canon/components/toast";import * as React from "react";export function WithClear() {    const owners = [        "Maya Okafor",        "Priya Raman",        "Tomás Ferreira",        "Jordan Lee",        "Aisha Bello",        "Wen Zhou",    ];    const id = React.useId();    const [owner, setOwner] = React.useState<string | null>("Priya Raman");    return (        <div className="flex w-full max-w-xs flex-col gap-1.5 text-left">            <Label htmlFor={id}>Exception owner</Label>            <Combobox                items={owners}                value={owner}                onValueChange={(next: string | null) => {                    setOwner(next);                    toast.add({                        title: next                            ? `${next} owns this exception`                            : "Owner removed",                        description: next                            ? undefined                            : "It goes back to the AP exceptions queue.",                    });                }}            >                <ComboboxInput                    id={id}                    placeholder="Unassigned"                    showClear                    className="w-full"                />                <ComboboxContent>                    <ComboboxEmpty>No one by that name.</ComboboxEmpty>                    <ComboboxList>                        {(name: string) => (                            <ComboboxItem key={name} value={name}>                                {name}                            </ComboboxItem>                        )}                    </ComboboxList>                </ComboboxContent>            </Combobox>        </div>    );}

Groups

Pass { value, items } groups to the root, then render each with ComboboxGroup, a ComboboxLabel and a ComboboxCollection. Empty groups drop out as you type.

import {  Combobox,  ComboboxCollection,  ComboboxContent,  ComboboxEmpty,  ComboboxGroup,  ComboboxInput,  ComboboxItem,  ComboboxLabel,  ComboboxList,  ComboboxSeparator,} from "@oration/canon/components/combobox";import { Label } from "@oration/canon/components/label";import { toast } from "@oration/canon/components/toast";import * as React from "react";export function Groups() {    const accounts = [        {            value: "Expenses",            items: [                "6100 Freight and shipping",                "6200 Software subscriptions",                "6300 Office supplies",                "6400 Professional fees",            ],        },        {            value: "Liabilities",            items: [                "2000 Accounts payable",                "2100 Accrued expenses",                "2200 Sales tax payable",            ],        },        {            value: "Assets",            items: ["1400 Prepaid expenses", "1500 Fixed assets"],        },    ];    const id = React.useId();    const [account, setAccount] = React.useState<string | null>(        "6100 Freight and shipping",    );    return (        <div className="flex w-full max-w-xs flex-col gap-1.5 text-left">            <Label htmlFor={id}>GL account</Label>            <Combobox                items={accounts}                value={account}                onValueChange={(next: string | null) => {                    setAccount(next);                    if (next)                        toast.add({ title: "Line coded", description: next });                }}            >                <ComboboxInput                    id={id}                    placeholder="Search accounts"                    className="w-full"                />                <ComboboxContent>                    <ComboboxEmpty>No accounts match.</ComboboxEmpty>                    <ComboboxList>                        {(                            group: { value: string; items: string[] },                            index: number,                        ) => (                            <ComboboxGroup                                key={group.value}                                items={group.items}                            >                                {index > 0 ? <ComboboxSeparator /> : null}                                <ComboboxLabel>{group.value}</ComboboxLabel>                                <ComboboxCollection>                                    {(name: string) => (                                        <ComboboxItem key={name} value={name}>                                            <span className="tabular-nums">                                                {name}                                            </span>                                        </ComboboxItem>                                    )}                                </ComboboxCollection>                            </ComboboxGroup>                        )}                    </ComboboxList>                </ComboboxContent>            </Combobox>        </div>    );}

Several values as chips

With multiple, chosen values become chips in the field and the list stays open. Share useComboboxAnchor() between ComboboxChips and ComboboxContent so the popup lines up with the whole box.

2 people get an email when the payment clears.

import {  Combobox,  ComboboxChip,  ComboboxChips,  ComboboxChipsInput,  ComboboxContent,  ComboboxEmpty,  ComboboxItem,  ComboboxList,  ComboboxValue,  useComboboxAnchor,} from "@oration/canon/components/combobox";import { Label } from "@oration/canon/components/label";import * as React from "react";export function MultipleWithChips() {    const people = [        "Maya Okafor",        "Priya Raman",        "Tomás Ferreira",        "Jordan Lee",        "Aisha Bello",        "Wen Zhou",    ];    const id = React.useId();    const anchor = useComboboxAnchor();    const [watchers, setWatchers] = React.useState<string[]>([        "Priya Raman",        "Wen Zhou",    ]);    return (        <div className="flex w-full max-w-sm flex-col gap-1.5 text-left">            <Label htmlFor={id}>Notify when paid</Label>            <Combobox                multiple                autoHighlight                items={people}                value={watchers}                onValueChange={setWatchers}            >                <ComboboxChips ref={anchor}>                    <ComboboxValue>                        {(values: string[]) => (                            <>                                {values.map((name) => (                                    <ComboboxChip key={name}>                                        {name}                                    </ComboboxChip>                                ))}                                <ComboboxChipsInput                                    id={id}                                    placeholder={                                        values.length ? "" : "Add people"                                    }                                />                            </>                        )}                    </ComboboxValue>                </ComboboxChips>                <ComboboxContent anchor={anchor}>                    <ComboboxEmpty>No one by that name.</ComboboxEmpty>                    <ComboboxList>                        {(name: string) => (                            <ComboboxItem key={name} value={name}>                                {name}                            </ComboboxItem>                        )}                    </ComboboxList>                </ComboboxContent>            </Combobox>            <p className="text-xs text-muted-foreground">                {watchers.length === 0                    ? "No one is notified. The supplier still gets the remittance."                    : `${watchers.length} ${watchers.length === 1 ? "person gets" : "people get"} an email when the payment clears.`}            </p>        </div>    );}

Add to a list

Keep value={null} and add the pick to your own list. The field clears for the next name, and the candidates shrink as people are added.

Payment run approvers

  • Priya RamanAccounts payable
  • Aisha BelloAccounts payable
import {  Combobox,  ComboboxContent,  ComboboxEmpty,  ComboboxInput,  ComboboxItem,  ComboboxList,} from "@oration/canon/components/combobox";import { toast } from "@oration/canon/components/toast";import * as React from "react";export function AddToList() {    type Member = { id: string; name: string; team: string };    const everyone: Member[] = [        { id: "maya", name: "Maya Okafor", team: "Revenue" },        { id: "priya", name: "Priya Raman", team: "Accounts payable" },        { id: "tomas", name: "Tomás Ferreira", team: "Treasury" },        { id: "jordan", name: "Jordan Lee", team: "Procurement" },        { id: "aisha", name: "Aisha Bello", team: "Accounts payable" },        { id: "wen", name: "Wen Zhou", team: "Controller" },    ];    const [memberIds, setMemberIds] = React.useState(["priya", "aisha"]);    const members = everyone.filter((m) => memberIds.includes(m.id));    const candidates = everyone.filter((m) => !memberIds.includes(m.id));    return (        <div className="w-full max-w-md rounded-xl bg-card text-left shadow-border">            <div className="flex flex-col gap-3 border-b border-border p-4">                <p className="text-sm font-medium">Payment run approvers</p>                <Combobox                    items={candidates}                    value={null}                    itemToStringLabel={(member: Member) => member.name}                    onValueChange={(member: Member | null) => {                        if (!member) return;                        setMemberIds((ids) => [...ids, member.id]);                        toast.add({                            type: "success",                            title: `Added ${member.name} as an approver`,                        });                    }}                >                    <ComboboxInput                        placeholder="Add an approver by name"                        aria-label="Add an approver"                        className="w-full"                    />                    <ComboboxContent>                        <ComboboxEmpty>                            Everyone is already an approver.                        </ComboboxEmpty>                        <ComboboxList>                            {(member: Member) => (                                <ComboboxItem key={member.id} value={member}>                                    <span className="flex-1 truncate">                                        {member.name}                                    </span>                                    <span className="text-xs text-muted-foreground">                                        {member.team}                                    </span>                                </ComboboxItem>                            )}                        </ComboboxList>                    </ComboboxContent>                </Combobox>            </div>            <ul className="flex flex-col p-2">                {members.map((member) => (                    <li                        key={member.id}                        className="flex h-9 items-center justify-between gap-3 rounded-lg px-2 text-13 hover:bg-muted"                    >                        <span className="truncate">{member.name}</span>                        <span className="text-xs text-muted-foreground">                            {member.team}                        </span>                    </li>                ))}            </ul>        </div>    );}

States#

Rest
Focus
Invalid
Disabled
import { Combobox, ComboboxInput } from "@oration/canon/components/combobox";import { cn } from "@oration/canon/lib/utils";export function StatesRow() {    const states = [        { name: "Rest", className: "", invalid: false, disabled: false },        {            name: "Focus",            className: "border-ring ring-3 ring-ring/50",            invalid: false,            disabled: false,        },        { name: "Invalid", className: "", invalid: true, disabled: false },        { name: "Disabled", className: "", invalid: false, disabled: true },    ];    return (        <div className="grid w-full grid-cols-2 gap-4 sm:grid-cols-4">            {states.map((state) => (                <div key={state.name} className="flex flex-col gap-1.5">                    <span className="text-xs text-muted-foreground">                        {state.name}                    </span>                    <Combobox                        items={["Halcyon"]}                        defaultValue="Halcyon"                        disabled={state.disabled}                    >                        <ComboboxInput                            aria-label={`Supplier, ${state.name.toLowerCase()}`}                            aria-invalid={state.invalid || undefined}                            disabled={state.disabled}                            tabIndex={-1}                            className={cn(                                "pointer-events-none w-full",                                state.className,                            )}                        />                    </Combobox>                </div>            ))}        </div>    );}
States
StateTreatment
RestTransparent field with the Field Stroke and a Slate Meta placeholder.
Focus visibleIndigo border and a 3px Focus Indigo ring at 50% on the whole group, drawn when the input has focus.
OpenThe popup fades and zooms in from 95% over 100ms, sliding 8px from the field.
HighlightedThe row under the pointer or arrow keys fills with the accent color.
SelectedA check at the right of the row.
EmptyComboboxEmpty shows its message in 14px Slate Meta when nothing matches the text.
InvalidWith aria-invalid on the input, a red border and a 3px red ring at 20% on the group or the chips box.
DisabledThe group drops to 50% opacity on a faint input fill. A disabled item drops to 50% and can't be highlighted.

Behavior#

  • Pass items to the root and a function as the child of ComboboxList; Base UI filters the items against the text and renders only the matches. For groups, pass { value, items }[] and render each with ComboboxGroup items and ComboboxCollection.
  • Objects work as items. Give the root itemToStringLabel so the input shows a name and the filter matches it; set isItemEqualToValue when the value and the items aren't the same objects.
  • Controlled with value and onValueChange, uncontrolled with defaultValue. Single mode reports null when cleared.
  • For an add control, keep value={null} and push the chosen item into your own list in onValueChange; the field clears for the next pick.
  • With multiple, the list stays open after each pick. Anchor the popup to the chips with useComboboxAnchor(), passing the ref to ComboboxChips and anchor to ComboboxContent.
  • Typing opens the list; autoHighlight highlights the first match so Enter picks it. Escape closes the list, and focus stays in the input.
  • The popup portals to the body and flips to stay in view.

Choosing a select#

Six components pick a value from a list. A time zone is always Timezone select. Past about 15 options, or for several values, use Combobox. Otherwise decide by what a row has to show and where the control sits.

Which select to use
ComponentReach for it whenList lengthRows show
SelectRows need icons, tags, groups or separators, the trigger shows a formatted value, or people pick several values from a short list. You compose the parts.2 to about 15Anything: icons, tags, two-line rows
Option selectA flat list of strings or { value, label } pairs in a dense inspector, run bar or toolbar. One line, full width, 13px.2 to about 15Text
Select fieldA typed value in a settings row, agent config page, sheet form or filter bar. The preset sets the width and height for that surface.2 to about 15Text
Native selectThe platform picker is the better control: phone-first forms, or a plain list that must post with a native form.Any, plain labelsText, grouped by optgroup
Combobox (this page)People, suppliers, invoices or any list long enough to search, or several values shown as removable chips.About 15 or more, or unknownAnything; filters as you type
Timezone selectA time zone. Always, instead of a hand-written list of zones.Every IANA zoneCity, zone, offset and local time

Do and don't#

Do. Use a combobox when people know the name and typing it is faster than scanning.
Don't. Put a search box on two options. It's a select with an extra step.
Do. Write an empty message that says what happened and what to try next.
Don't. Leave ComboboxEmpty out. When nothing matches, the popup collapses to nothing and people think it broke.

Content#

  • The label names the field: Approver, Supplier, GL account.
  • The placeholder says what to type: Search people, Add an approver by name. Not Select….
  • The empty message names the miss and a way forward: No suppliers match. Check the spelling or add one.
  • A trailing detail in a row, such as the team, is 12px Slate Meta and helps tell two similar names apart.
  • In an add control, say when there's no one left: Everyone is already an approver.

Accessibility#

  • The input has role="combobox" with aria-expanded, aria-controls and aria-activedescendant, so focus stays in the text while arrow keys move through the list.
  • Name the input with a Label linked by id, or aria-label when there's no visible label.
  • The chevron trigger and the chip remove buttons are out of the tab order (tabIndex=-1); keyboard users open with the arrow keys and remove chips with Backspace.
  • Set aria-invalid on the input for errors and link the message with aria-describedby.
  • Every item in a list of objects needs a unique key and a readable string label for the input.
Keyboard interactions
KeysAction
↓Opens the list, then moves the highlight down.
↑Moves the highlight up.
EnterChooses the highlighted item. In multiple mode, toggles it.
EscCloses the list. Focus stays in the input.
←With chips, from the start of the text, highlights the last chip; then moves between chips.
BackspaceWith chips, removes the highlighted chip, or the last chip when the text is empty.

Design tokens#

Design tokens
TokenUsed for
--inputField Stroke; 30% fill in dark and disabled
--ringFocus border and 3px ring at 50%
--destructiveInvalid border and ring
--popoverPopup surface
shadow-mdPopup lift
--accentHighlighted row
--mutedChip fill
--muted-foregroundPlaceholder, chevron, group label, empty message
--radius-lg10px field and popup corners

API reference#

Combobox

The root, Base UI Combobox.Root as is. Holds the value, the text and the open state.

Other props spread onto Base UI Combobox.Root.

Props of Combobox
PropTypeDefaultDescription
itemsreadonly Item[] | readonly { value: string; items: Item[] }[]No defaultThe list to filter. Groups use { value, items } objects.
valueValue | Value[] | nullNo defaultControlled value. An array with multiple.
defaultValueValue | Value[] | nullNo defaultInitial value when uncontrolled.
onValueChange(value, eventDetails) => voidNo defaultCalled with the new value, or null when cleared.
multiplebooleanfalseAllows several values, usually rendered as chips.
itemToStringLabel(item: Value) => stringNo defaultThe text for an object item, shown in the input and used by the filter.
isItemEqualToValue(item: Value, value: Value) => booleanNo defaultCompares items to the value when they aren't the same objects.
filter((item, query) => boolean) | nullNo defaultReplaces the default filter. null turns it off, for server search.
inputValue / onInputValueChangestring / (value: string) => voidNo defaultControl the text, for example to search a server.
open / onOpenChangeboolean / (open: boolean) => voidNo defaultControl the popup.
autoHighlightbooleanfalseHighlights the first match as people type.
disabledbooleanfalseDisables the whole control.

ComboboxInput

The text field, an Input group with the chevron trigger and an optional clear button. className goes to the group.

Other props spread onto Base UI Combobox.Input.

Props of ComboboxInput
PropTypeDefaultDescription
showTriggerbooleantrueShows the chevron button that opens the list.
showClearbooleanfalseShows a clear button in place of the chevron once there is a value.
disabledbooleanfalseDisables the input and its buttons.
placeholderstringNo defaultWhat to type, such as Search people.
childrenReactNodeNo defaultExtra addons rendered inside the group.

ComboboxContent

The portal, positioner and popup.

Other props spread onto Base UI Combobox.Popup.

Props of ComboboxContent
PropTypeDefaultDescription
side"top" | "bottom" | "left" | "right" | "inline-start" | "inline-end""bottom"Preferred side. It flips to stay in view.
sideOffsetnumber6Gap from the field in px.
align"start" | "center" | "end""start"Alignment along the field.
alignOffsetnumber0Offset along the alignment axis.
anchorRefObject<HTMLDivElement | null>No defaultThe element to position against. Pass the useComboboxAnchor() ref used on ComboboxChips.

ComboboxList

The scrolling list. Pass a function child (item, index) => ReactNode to render filtered items.

Other props spread onto Base UI Combobox.List.

No props of its own.

ComboboxItem

A row with the check indicator.

Other props spread onto Base UI Combobox.Item.

Props of ComboboxItem
PropTypeDefaultDescription
valueRequiredValueNo defaultThe item this row picks.
disabledbooleanfalseShown at 50% and skipped by the highlight.

ComboboxGroup

A group of rows. Pass the group's items for ComboboxCollection.

Other props spread onto Base UI Combobox.Group.

Props of ComboboxGroup
PropTypeDefaultDescription
itemsreadonly Item[]No defaultThe group's items after filtering.

ComboboxLabel

A 12px Slate Meta group heading.

Other props spread onto Base UI Combobox.GroupLabel.

No props of its own.

ComboboxCollection

Renders a group's items with a function child.

Other props spread onto Base UI Combobox.Collection.

No props of its own.

ComboboxEmpty

The message shown when nothing matches.

Other props spread onto Base UI Combobox.Empty.

No props of its own.

ComboboxSeparator

A hairline between groups.

Other props spread onto Base UI Combobox.Separator.

No props of its own.

ComboboxChips

The field for multiple: a wrapping box that holds chips and the chips input, with the focus and invalid rings.

Other props spread onto Base UI Combobox.Chips.

Props of ComboboxChips
PropTypeDefaultDescription
refRefObject<HTMLDivElement | null>No defaultPass the useComboboxAnchor() ref.

ComboboxChip

One chosen value with a remove button.

Other props spread onto Base UI Combobox.Chip.

Props of ComboboxChip
PropTypeDefaultDescription
showRemovebooleantrueShows the remove button.

ComboboxChipsInput

The text input inside the chips box, at least 64px wide.

Other props spread onto Base UI Combobox.Input.

No props of its own.

ComboboxValue

Renders the current value. With chips, pass a function child (values) => ReactNode.

Other props spread onto Base UI Combobox.Value.

No props of its own.

ComboboxTrigger

A button that opens the list, with a chevron appended. ComboboxInput renders one for you.

Other props spread onto Base UI Combobox.Trigger.

No props of its own.

useComboboxAnchor

Returns a RefObject<HTMLDivElement | null> to share between ComboboxChips and ComboboxContent.

No props of its own.

Known gaps#

Where the implementation and the system disagree today. Follow the system, not the gap.

The popup draws its edge with ring-1 plus shadow-md rather than the single composite overlay shadow the Hairline-and-Lift Rule asks for.

ComboboxChip is 21px with 6px corners, off the tag ramp (20px, 8px corners). Chips aren't tags, but the size isn't on any ramp.

The chip remove button has no accessible name. It is out of the tab order, so keyboard users remove chips with Backspace, but screen reader users browsing the field meet an unnamed button.

ComboboxClear isn't exported. Clearing is only available through showClear on ComboboxInput, so the chips field has no clear-all button.

Three product files use it, all in team settings, so its patterns are less proven than Select's.