Propel

Combobox

A closed-field picker that selects one or more options from a searchable overlay panel — the same shell the dropdown menu uses.

Show code
import {
  Combobox,
  ComboboxContent,
  ComboboxEmpty,
  ComboboxItem,
  ComboboxList,
  ComboboxSearch,
  ComboboxSelectAll,
  ComboboxSeparator,
  ComboboxStickyList,
  ComboboxTrigger,
} from "@makeplane/propel/components/combobox";
import { Field } from "@makeplane/propel/components/field";
import { Icon } from "@makeplane/propel/components/icon";
import { FieldLabel } from "@makeplane/propel/elements/field";
import { Plus } from "lucide-react";
import { useId, useState } from "react";

const REGIONS = ["us-central-1", "us-east-1", "eu-central-1", "ap-west-1"];

export default function BasicDemo() {
  const labelId = useId();
  const triggerId = useId();
  const [value, setValue] = useState<string[]>([]);
  const allSelected = REGIONS.every((region) => value.includes(region));
  const someSelected = !allSelected && value.length > 0;
  return (
    <Field name="region">
      <Combobox multiple items={REGIONS} value={value} onValueChange={setValue}>
        <FieldLabel id={labelId} size="lg" inset={false} htmlFor={triggerId}>
          Region
        </FieldLabel>
        <ComboboxTrigger
          id={triggerId}
          size="lg"
          placeholder="Select label"
          icon={<Icon icon={Plus} tint="placeholder" />}
        />
        <ComboboxContent
          aria-labelledby={labelId}
          search={
            <>
              <ComboboxSearch aria-label="Search" />
              <ComboboxStickyList>
                <ComboboxSelectAll
                  checked={allSelected}
                  indeterminate={someSelected}
                  onClick={() => setValue(allSelected ? [] : [...REGIONS])}
                />
                <ComboboxSeparator />
              </ComboboxStickyList>
            </>
          }
        >
          <ComboboxEmpty>No matches</ComboboxEmpty>
          <ComboboxList>
            {(region: string) => (
              <ComboboxItem key={region} value={region} selection="checkbox" label={region} />
            )}
          </ComboboxList>
        </ComboboxContent>
      </Combobox>
    </Field>
  );
}

Installation

import {
  Combobox,
  ComboboxContent,
  ComboboxEmpty,
  ComboboxItem,
  ComboboxList,
  ComboboxSearch,
  ComboboxSelectAll,
  ComboboxTrigger,
} from "@makeplane/propel/components/combobox";

Usage

import {
  Combobox,
  ComboboxContent,
  ComboboxEmpty,
  ComboboxItem,
  ComboboxList,
  ComboboxSearch,
  ComboboxSelectAll,
  ComboboxSeparator,
  ComboboxStickyList,
  ComboboxTrigger,
} from "@makeplane/propel/components/combobox";
import { Field } from "@makeplane/propel/components/field";
import { Icon } from "@makeplane/propel/components/icon";
import { FieldLabel } from "@makeplane/propel/elements/field";
import { Plus } from "lucide-react";
import { useId, useState } from "react";

const REGIONS = ["us-central-1", "us-east-1", "eu-central-1", "ap-west-1"];

export default function BasicDemo() {
  const labelId = useId();
  const triggerId = useId();
  const [value, setValue] = useState<string[]>([]);
  const allSelected = REGIONS.every((region) => value.includes(region));
  const someSelected = !allSelected && value.length > 0;
  return (
    <Field name="region">
      <Combobox multiple items={REGIONS} value={value} onValueChange={setValue}>
        <FieldLabel id={labelId} size="lg" inset={false} htmlFor={triggerId}>
          Region
        </FieldLabel>
        <ComboboxTrigger
          id={triggerId}
          size="lg"
          placeholder="Select label"
          icon={<Icon icon={Plus} tint="placeholder" />}
        />
        <ComboboxContent
          aria-labelledby={labelId}
          search={
            <>
              <ComboboxSearch aria-label="Search" />
              <ComboboxStickyList>
                <ComboboxSelectAll
                  checked={allSelected}
                  indeterminate={someSelected}
                  onClick={() => setValue(allSelected ? [] : [...REGIONS])}
                />
                <ComboboxSeparator />
              </ComboboxStickyList>
            </>
          }
        >
          <ComboboxEmpty>No matches</ComboboxEmpty>
          <ComboboxList>
            {(region: string) => (
              <ComboboxItem key={region} value={region} selection="checkbox" label={region} />
            )}
          </ComboboxList>
        </ComboboxContent>
      </Combobox>
    </Field>
  );
}

Examples

Variants

variant sets the frame’s look on ComboboxTrigger, ComboboxInputGroup, ComboboxChips and ComboboxField: neutral is the bordered field surface (the default), and ghost is borderless, filling only on hover, focus and while the popup is open. Use ghost where the control sits inside an already-bordered container — a toolbar or a table cell — so the frame does not double up.

Show code
import {
  Combobox,
  ComboboxContent,
  ComboboxEmpty,
  ComboboxItem,
  ComboboxList,
  ComboboxSearch,
  ComboboxTrigger,
  type ComboboxVariant,
} from "@makeplane/propel/components/combobox";
import { Field } from "@makeplane/propel/components/field";
import { FieldLabel } from "@makeplane/propel/elements/field";
import { useId } from "react";

const REGIONS = ["us-central-1", "us-east-1", "eu-central-1", "ap-west-1"];

const VARIANTS: ComboboxVariant[] = ["neutral", "ghost"];

function VariantField({ variant }: { variant: ComboboxVariant }) {
  const labelId = useId();
  const triggerId = useId();
  return (
    <Field name={`region-${variant}`}>
      <Combobox items={REGIONS} multiple>
        <FieldLabel id={labelId} size="lg" inset={false} htmlFor={triggerId}>
          {variant}
        </FieldLabel>
        <ComboboxTrigger id={triggerId} size="lg" variant={variant} placeholder="Select label" />
        <ComboboxContent aria-labelledby={labelId} search={<ComboboxSearch aria-label="Search" />}>
          <ComboboxEmpty>No matches</ComboboxEmpty>
          <ComboboxList>
            {(region: string) => (
              <ComboboxItem key={region} value={region} selection="checkbox" label={region} />
            )}
          </ComboboxList>
        </ComboboxContent>
      </Combobox>
    </Field>
  );
}

export default function VariantsDemo() {
  return (
    <div className="flex flex-wrap items-end gap-3">
      {VARIANTS.map((variant) => (
        <VariantField key={variant} variant={variant} />
      ))}
    </div>
  );
}

Chips in the field

Default is already multi-select (closed trigger + overlay). Pass multiple with ComboboxChips when the filter input lives in the field: each selected value becomes a removable chip ahead of the inline input. Option rows use selection="checkbox" so they match the dropdown checkbox items.

Show code
import {
  Combobox,
  ComboboxChip,
  ComboboxChips,
  ComboboxContent,
  ComboboxEmpty,
  ComboboxItem,
  ComboboxList,
} from "@makeplane/propel/components/combobox";
import { Field, FieldLabel } from "@makeplane/propel/components/field";
import { Icon } from "@makeplane/propel/components/icon";
import { IconButton } from "@makeplane/propel/components/icon-button";
import { X } from "lucide-react";

const REGIONS = ["us-central-1", "us-east-1", "eu-central-1", "ap-west-1"];

export default function MultipleDemo() {
  return (
    <Field name="regions">
      <Combobox multiple items={REGIONS} defaultValue={["us-east-1", "eu-central-1"]}>
        <FieldLabel size="lg" inset={false}>
          Regions
        </FieldLabel>
        <ComboboxChips size="lg" placeholder="Add a region">
          {(region: string) => (
            <ComboboxChip
              key={region}
              label={region}
              remove={
                <IconButton
                  variant="ghost"
                  size="xs"
                  aria-label={`Remove ${region}`}
                  icon={<Icon icon={X} />}
                />
              }
            />
          )}
        </ComboboxChips>
        <ComboboxContent>
          <ComboboxEmpty>No matches</ComboboxEmpty>
          <ComboboxList>
            {(region: string) => (
              <ComboboxItem key={region} value={region} selection="checkbox" label={region} />
            )}
          </ComboboxList>
        </ComboboxContent>
      </Combobox>
    </Field>
  );
}

Limiting visible chips

maxVisible collapses a large selection to a single row — the first N chips plus a “+N more” count — instead of wrapping onto new rows; the hidden values stay managed from the popup. Pass overflowLabel to localize that count.

Show code
import {
  Combobox,
  ComboboxChip,
  ComboboxChips,
  ComboboxContent,
  ComboboxEmpty,
  ComboboxItem,
  ComboboxList,
} from "@makeplane/propel/components/combobox";
import { Field, FieldLabel } from "@makeplane/propel/components/field";
import { Icon } from "@makeplane/propel/components/icon";
import { IconButton } from "@makeplane/propel/components/icon-button";
import { X } from "lucide-react";

const REGIONS = [
  "us-central-1",
  "us-east-1",
  "eu-central-1",
  "eu-west-1",
  "ap-west-1",
  "ap-southeast-2",
  "sa-east-1",
];

export default function MaxVisibleDemo() {
  return (
    <Field name="regions">
      <Combobox multiple items={REGIONS} defaultValue={REGIONS}>
        <FieldLabel size="lg" inset={false}>
          Regions
        </FieldLabel>
        <ComboboxChips size="lg" placeholder="Add a region" maxVisible={2}>
          {(region: string) => (
            <ComboboxChip
              key={region}
              label={region}
              remove={
                <IconButton
                  variant="ghost"
                  size="xs"
                  aria-label={`Remove ${region}`}
                  icon={<Icon icon={X} />}
                />
              }
            />
          )}
        </ComboboxChips>
        <ComboboxContent>
          <ComboboxEmpty>No matches</ComboboxEmpty>
          <ComboboxList>
            {(region: string) => (
              <ComboboxItem key={region} value={region} selection="checkbox" label={region} />
            )}
          </ComboboxList>
        </ComboboxContent>
      </Combobox>
    </Field>
  );
}

Grouped

Show code
import {
  Combobox,
  ComboboxCollection,
  ComboboxContent,
  ComboboxEmpty,
  ComboboxGroup,
  ComboboxGroupLabel,
  ComboboxInputGroup,
  ComboboxItem,
  ComboboxList,
} from "@makeplane/propel/components/combobox";
import { Field, FieldLabel } from "@makeplane/propel/components/field";

const GROUPED_REGIONS = [
  { label: "Americas", items: ["us-central-1", "us-east-1", "sa-east-1"] },
  { label: "Europe", items: ["eu-central-1", "eu-west-1"] },
  { label: "Asia Pacific", items: ["ap-west-1", "ap-southeast-2"] },
];

export default function GroupedDemo() {
  return (
    <Field name="region">
      <Combobox items={GROUPED_REGIONS}>
        <FieldLabel size="lg" inset={false}>
          Region
        </FieldLabel>
        <ComboboxInputGroup size="lg" placeholder="e.g. eu-central-1" />
        <ComboboxContent>
          <ComboboxEmpty>No matches</ComboboxEmpty>
          <ComboboxList>
            {(group: (typeof GROUPED_REGIONS)[number]) => (
              <ComboboxGroup key={group.label} items={group.items}>
                <ComboboxGroupLabel>{group.label}</ComboboxGroupLabel>
                <ComboboxCollection>
                  {(region: string) => <ComboboxItem key={region} value={region} label={region} />}
                </ComboboxCollection>
              </ComboboxGroup>
            )}
          </ComboboxList>
        </ComboboxContent>
      </Combobox>
    </Field>
  );
}

filter={null} turns off built-in filtering so onInputValueChange can drive a remote search, with ComboboxStatus carrying the polite loading and empty-query hints. useFilter and useFilteredItems are re-exported from the same module for custom filter implementations.

Show code
import {
  Combobox,
  ComboboxContent,
  ComboboxEmpty,
  ComboboxInputGroup,
  ComboboxItem,
  ComboboxList,
  ComboboxStatus,
  useFilter,
} from "@makeplane/propel/components/combobox";
import { Field, FieldLabel } from "@makeplane/propel/components/field";
import { Icon } from "@makeplane/propel/components/icon";
import { IconButton } from "@makeplane/propel/components/icon-button";
import { X } from "lucide-react";
import * as React from "react";

const MEMBERS = ["Aaditya Kapoor", "Bianca Ferreira", "Marcus Chen", "Priya Nair", "Rohan Sharma"];

export default function AsyncSearchDemo() {
  const [query, setQuery] = React.useState("");
  const [results, setResults] = React.useState<string[]>([]);
  const [pending, setPending] = React.useState(false);
  const timeoutRef = React.useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
  const { contains } = useFilter();

  React.useEffect(() => () => clearTimeout(timeoutRef.current), []);

  const trimmed = query.trim();
  const status = pending ? "Searching…" : trimmed === "" ? "Start typing to search members" : null;

  return (
    <Field name="reviewer">
      <Combobox
        items={results}
        // Built-in filtering is off: the (fake) server already returns only the matches.
        filter={null}
        onInputValueChange={(nextQuery, { reason }) => {
          setQuery(nextQuery);
          if (reason === "item-press") {
            return;
          }
          clearTimeout(timeoutRef.current);
          const trimmedQuery = nextQuery.trim();
          if (trimmedQuery === "") {
            setResults([]);
            setPending(false);
            return;
          }
          setPending(true);
          // Deterministic stand-in for a server search: resolves after a short delay.
          timeoutRef.current = setTimeout(() => {
            setResults(MEMBERS.filter((member) => contains(member, trimmedQuery)));
            setPending(false);
          }, 300);
        }}
      >
        <FieldLabel size="lg" inset={false}>
          Reviewer
        </FieldLabel>
        <ComboboxInputGroup
          size="lg"
          placeholder="e.g. Priya"
          clear={
            <IconButton
              variant="ghost"
              size="md"
              aria-label="Clear reviewer"
              icon={<Icon icon={X} />}
            />
          }
        />
        <ComboboxContent>
          <ComboboxStatus>{status}</ComboboxStatus>
          <ComboboxEmpty>{!pending && trimmed !== "" ? "No members match" : null}</ComboboxEmpty>
          <ComboboxList>
            {(member: string) => <ComboboxItem key={member} value={member} label={member} />}
          </ComboboxList>
        </ComboboxContent>
      </Combobox>
    </Field>
  );
}

Sizes

size steps the trigger’s height, text, and glyph scale: md, lg, xl, or 2xl. variant="ghost" drops the bordered chrome; invalid on ghost has no trigger-level change (the danger cue is the helper text).

ComboboxContent opens inside the same overlay panel MenuContent uses. Pin a ComboboxSearch in that panel with search={<ComboboxSearch />} — that search row is the combobox input, so don’t also mount ComboboxInputGroup / ComboboxChips. Name the dialog with aria-labelledby pointing at the visible field label (or aria-label).

Show code
import {
  Combobox,
  ComboboxContent,
  ComboboxEmpty,
  ComboboxItem,
  ComboboxList,
  ComboboxSearch,
  ComboboxTrigger,
} from "@makeplane/propel/components/combobox";
import { Field } from "@makeplane/propel/components/field";
import { FieldLabel } from "@makeplane/propel/elements/field";
import { useId } from "react";

const REGIONS = ["us-central-1", "us-east-1", "eu-central-1", "ap-west-1"];

const SIZES = ["md", "lg", "xl", "2xl"] as const;

function SizeField({ size }: { size: (typeof SIZES)[number] }) {
  const labelId = useId();
  const triggerId = useId();
  return (
    <Field name={`region-${size}`}>
      <Combobox items={REGIONS} multiple>
        <FieldLabel id={labelId} size="lg" inset={false} htmlFor={triggerId}>
          {size}
        </FieldLabel>
        <ComboboxTrigger id={triggerId} size={size} placeholder="Select label" />
        <ComboboxContent aria-labelledby={labelId} search={<ComboboxSearch aria-label="Search" />}>
          <ComboboxEmpty>No matches</ComboboxEmpty>
          <ComboboxList>
            {(region: string) => (
              <ComboboxItem key={region} value={region} selection="checkbox" label={region} />
            )}
          </ComboboxList>
        </ComboboxContent>
      </Combobox>
    </Field>
  );
}

export default function SizesDemo() {
  return (
    <div className="flex flex-wrap items-end gap-3">
      {SIZES.map((size) => (
        <SizeField key={size} size={size} />
      ))}
    </div>
  );
}

API Reference

Combobox

The combobox Root — Base UI's context/state provider (renders no element of its own). A behavior-only role, so it lives in `components` (rules 1a, 2); the styled parts live in `elements/combobox` and are grafted onto Base UI behavior here.

PropTypeDefaultDescription
autoCompletestringProvides a hint to the browser for autofill. @see https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Attributes/autocomplete
autoHighlightbooleanfalseWhether the first matching item is highlighted automatically while filtering.
highlightItemOnHoverbooleantrueWhether moving the pointer over items should highlight them. Disabling this prop allows CSS `:hover` to be differentiated from the `:focus` (`data-highlighted`) state.
itemToStringLabel((itemValue: Value) => string)When the item values are objects (`<Combobox.Item value={object}>`), this function converts the object value to a string representation for display in the input. If the shape of the object is `{ value, label }`, the label will be used automatically without needing to specify this prop.
itemToStringValue((itemValue: Value) => string)When the item values are objects (`<Combobox.Item value={object}>`), this function converts the object value to a string representation for form submission. If the shape of the object is `{ value, label }`, the value will be used automatically without needing to specify this prop.
isItemEqualToValue((itemValue: Value, value: Value) => boolean)Custom comparison logic used to determine if a combobox item value matches the current selected value. Useful when item values are objects without matching referentially. Defaults to `Object.is` comparison.
actionsRefRefObject<Actions | null>A ref to imperative actions. - `unmount`: Manually unmounts the combobox. Call this after any externally controlled closing animation finishes.
onOpenChange((open: boolean, eventDetails: ChangeEventDetails) => void)Event handler called when the popup is opened or closed.
onInputValueChange((inputValue: string, eventDetails: ChangeEventDetails) => void)Event handler called when the input value changes.
onItemHighlighted((highlightedValue: Value, eventDetails: HighlightEventDetails) => void)Callback fired when an item is highlighted or unhighlighted. Receives the highlighted item value (or `undefined` if no item is highlighted) and event details with a `reason` property describing why the highlight changed. The `reason` can be: - `'keyboard'`: the highlight changed due to keyboard navigation. - `'pointer'`: the highlight changed due to pointer hovering. - `'none'`: the highlight changed programmatically.
multiplebooleanfalseWhether multiple items can be selected.
defaultValueComboboxValueType<Value, Multiple> | nullThe uncontrolled selected value of the combobox when it's initially rendered. To render a controlled combobox, use the `value` prop instead.
valueComboboxValueType<Value, Multiple> | nullThe selected value of the combobox. Use when controlled.
onValueChange((value: ComboboxValueType<Value, Multiple> | (Multiple extends true ? never : null), eventDetails: ChangeEventDetails) => void)Event handler called when the selected value of the combobox changes.

ComboboxTrigger

The ready-made closed-field trigger: grafts Base UI's `Trigger` onto the styled frame and shows the selected value (or `placeholder`) plus a trailing chevron. Use this instead of `ComboboxInputGroup` when the filter input lives in the panel (`ComboboxSearch`).

PropTypeDefaultDescription
size(required)"2xl" | "md" | "lg" | "xl"Visual size of the trigger: height, text, and glyph sizing. Required.
variant"neutral" | "ghost""neutral"Frame look. `neutral` is the bordered field surface; `ghost` is borderless. Invalid on ghost has no trigger chrome — the danger cue is the helper text below.
placeholderReactNodeShown in the trigger while nothing is selected — Base UI's `Combobox.Value` placeholder.
iconReactNodeDecorative leading element at the inline-start, e.g. `<Icon icon={Plus} tint="placeholder" />`.
trailingReactNodeDecorative trailing glyph. Omit it to show a chevron (the Figma end node). This is not a nested popup-trigger control — the frame itself is the `Trigger`.

ComboboxInputGroup

The ready-made single-select input frame: grafts Base UI's `InputGroup` onto the styled frame and Base UI's `Input` onto the styled text field, laying out an optional leading `icon` slot and the consumer-provided `clear`/`trigger` controls. `clear` stays `keepMounted` in a reserved slot so the trailing trigger does not shift when the value is empty. When `trigger` is omitted, a decorative chevron is shown. All remaining props pass through to the input (the element that carries the combobox behavior and accessible name).

PropTypeDefaultDescription
size(required)"2xl" | "md" | "lg" | "xl"Visual size of the input row: height, text, and glyph sizing. Required.
placeholderstringInput placeholder.
variant"neutral" | "ghost""neutral"Frame look. `neutral` is the bordered field surface; `ghost` is borderless.
iconReactNodeDecorative leading element at the inline-start, e.g. `<Icon icon={Search} tint="placeholder" />`.
clearReactElement<unknown, string | JSXElementConstructor<any>>The clear control (e.g. an `IconButton`), grafted onto Base UI's `Clear` behavior. It carries its own — localizable — `aria-label`; the group bakes no label or glyph.
triggerReactElement<unknown, string | JSXElementConstructor<any>>The popup-trigger control (e.g. an `IconButton`), grafted onto Base UI's `Trigger` behavior. It carries its own — localizable — `aria-label`; the group bakes no label or glyph. Omit it to show a decorative chevron (the Figma end node) — the input still opens the list.

ComboboxChips

The ready-made multiselect input frame — it replaces `ComboboxInputGroup` in the `multiple` anatomy. Grafts Base UI's `Chips` behavior onto the styled `elements/combobox` frame, owning only the frame, the selected-values loop, and `maxVisible` overflow ahead of the inline input — each chip's template is the consumer's `children`, mirroring `ComboboxList`. Without `maxVisible` the chips wrap onto new rows; with it the frame collapses to one row of the first `maxVisible` chips plus an overflow count (`+N more` by default; pass `overflowLabel` to localize). Arrow keys move focus across chips; Backspace removes.

PropTypeDefaultDescription
size(required)"2xl" | "md" | "lg" | "xl"Visual size of the chips frame: height, text, and glyph sizing. Required.
children(required)(value: Value) => ReactNodeRenders one selected value as a `ComboboxChip` — the `ComboboxChips` counterpart of `ComboboxList`'s item template. Set your own `key`, exactly like `ComboboxList`'s function child, e.g. `(value) => <ComboboxChip key={value} label={value} remove={<... />} />`.
variant"neutral" | "ghost""neutral"Frame look. `neutral` is the bordered field surface; `ghost` is borderless.
placeholderstringInput placeholder shown while typing to add another value.
maxVisiblenumberCap on how many chips render inline. When more are selected, the frame collapses to a single row showing the first `maxVisible` chips followed by an overflow count (the rest stay managed from the popup). Omit for the default behavior — chips wrap onto new rows as the selection grows.
overflowLabel((hiddenCount: number) => ReactNode)(hiddenCount) => `+${hiddenCount} more`Copy for the overflow count when `maxVisible` hides selected chips. Called with how many values are hidden. Override to localize.
triggerReactElement<unknown, string | JSXElementConstructor<any>>The popup-trigger control (e.g. an `IconButton`), grafted onto Base UI's `Trigger` behavior. It carries its own — localizable — `aria-label`. Omit it to show a decorative chevron (the Figma end node) — the input still opens the list.

ComboboxChip

Ready-made chip: Base UI's chip behavior (arrow-key focus, Backspace/Delete removal) grafted onto the styled tag, laying out an optional leading/trailing node around the label plus the consumer-provided `remove` control. Base UI derives which selected value a chip represents from its position among its siblings — pass one per value from `ComboboxChips`' `children`.

PropTypeDefaultDescription
label(required)stringThe chip's label. Also sets the chip's own accessible name.
remove(required)ReactElement<unknown, string | JSXElementConstructor<any>>The remove control, grafted onto Base UI's `ChipRemove` behavior. It carries its own — localizable — `aria-label`; the chip bakes no label or glyph.
renderReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, ComboboxChipState>Allows you to replace the component's HTML element with a different tag, or compose it with another component. Accepts a `ReactElement` or a function that returns the element to render.
size"xs" | "sm" | "md" | "lg"Chip height, text and glyph size. Inherited from the enclosing `ComboboxChips` when omitted.
startContentReactNodeLeading element before the label — an `<Icon icon={...} />`, an assignee `Avatar`, or similar.
endContentReactNodeTrailing element after the label, before the remove control, e.g. a status `<Icon .../>`.

ComboboxContent

The combobox list surface: Base UI portal + positioner, with `Combobox.Popup` grafted onto the same elevated `OverlayPanel` `MenuContent` uses. Search/footer therefore live _inside_ the dialog (unlike Menu, where they must sit outside `role="menu"`). The inner `ComboboxPopup` is padding-only; sticky chrome stays pinned outside the scroll area. When the input is in the panel (`ComboboxSearch` in `search`), the overlay is a dialog — name it with `aria-labelledby` on the visible field label, or `aria-label`. Filter-in-field compositions (`ComboboxInputGroup` / `ComboboxChips`) keep `role="presentation"` and need no dialog name.

PropTypeDefaultDescription
side"top" | "bottom" | "left" | "right" | "inline-end" | "inline-start"bottomWhich side of the input the list opens toward.
sideOffsetnumber | OffsetFunction4Distance in px between the input and the list.
align"center" | "start" | "end"startAlignment of the list relative to the input along `side`.
alignOffsetnumber | OffsetFunction0Additional offset in px along the align axis — slides the list along the input's edge without changing `align`.
collisionPaddingPadding5Minimum gap in px between the list and the viewport edge it would otherwise touch.
collisionBoundaryBoundaryThe element or rect the list is confined to when avoiding collisions. Base UI's clipping ancestors when unset.
collisionAvoidanceCollisionAvoidanceWhat to do when the list would overflow the boundary, set per axis: flip to the other side, shift along it, or stay put. Base UI flips the side and shifts the alignment when unset.
stickybooleanfalseKeep the list on screen after the input scrolls out of view, instead of following it out.
positionMethod"absolute" | "fixed""absolute"Which CSS `position` the positioner uses. `fixed` escapes overflow-clipping ancestors, e.g. an input inside a sticky header.
anchorElement | VirtualElement | RefObject<Element | null> | (() => Element | VirtualElement | null) | nullPosition against this element instead of the input — an element, a ref, a virtual element, or a function returning one. Geometry only; the input keeps its role and interactions.
disableAnchorTrackingbooleanfalseStop tracking layout shifts of the anchor — position once, on open.
sizing"menu" | "auto" | "anchor" | "sm" | "md" | "lg"anchorHow the popup is sized — width for every value, plus a height cap for `menu`. `anchor` matches the input's width, `sm`/`md`/`lg` are the fixed picker widths (256/288/384px), `auto` hugs the content with a small floor, and `menu` is the dropdown's own 192–320px width range with a 384px height cap.
searchReactNodeSticky chrome pinned above the list, e.g. a `ComboboxSearch`. When the input lives in this slot, the overlay is a `role="dialog"` and needs a name — pass `aria-labelledby` pointing at the visible field label, or `aria-label`.
footerReactNodeSticky chrome pinned below the list.

ComboboxSearch

A sticky search input pinned above a `ComboboxContent` list. Grafts Base UI's `Input` onto the same search-row chrome `MenuSearch` uses, so the combobox panel matches the dropdown. This _is_ the combobox's `Input` — compose it via `ComboboxContent`'s `search` slot instead of `ComboboxInputGroup` / `ComboboxChips`, which already mount an input in the field. Defaults to `aria-label="Search"` (or the `placeholder`, if you changed that) so the combobox stays named.

PropTypeDefaultDescription
placeholderstringSearchPlaceholder text.
aria-labelstring"Search"Defines a string value that labels the current element. Accessible name for the search combobox. Placeholder is not a name — override to localize, or pass `aria-labelledby` instead. @see aria-labelledby.

ComboboxSelectAll

Sticky "select every item" row for a multi-select combobox panel. Not a `ComboboxItem` — it is not a list option — so compose it in `ComboboxContent`'s `search` slot above the scrolling list.

PropTypeDefaultDescription
checked(required)booleanWhether every item is currently selected.
onClick(required)() => voidCalled when the row is activated.
labelstringSelect allVisible row label.
indeterminatebooleanfalseWhether only some (not all) items are currently selected — shows a dash instead of a check. Takes precedence over `checked` for both the glyph and `aria-checked="mixed"`.
disabledbooleanDisables the row.

ComboboxStickyList

Pads sticky header rows (Select all + separator) with the same inset the scrolling list uses.

PropTypeDefaultDescription
renderReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, {}>Allows you to replace the component's HTML element with a different tag, or compose it with another component. Accepts a `ReactElement` or a function that returns the element to render.

ComboboxSeparator

A thin divider between groups of items in a combobox list — the same chrome as `MenuSeparator`. Base UI's combobox has no Separator primitive, so this is the styled element composed into the list.

PropTypeDefaultDescription
renderReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, {}>Allows you to replace the component's HTML element with a different tag, or compose it with another component. Accepts a `ReactElement` or a function that returns the element to render.

ComboboxList

The combobox items container — Base UI's `List` behavior part (it carries no propel styling of its own), passed through so a full combobox composes without importing `@base-ui/react`. Inside `ComboboxContent`'s overlay, this is the `role="listbox"` node; it defaults to `aria-label="Suggestions"` so the listbox stays named (pass `aria-label` / `aria-labelledby` to override or localize).

PropTypeDefaultDescription
aria-labelstringSuggestionsDefines a string value that labels the current element. Accessible name for the listbox. Override to localize, or pass `aria-labelledby` instead. @see aria-labelledby.
renderReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, ComboboxListState>Allows you to replace the component's HTML element with a different tag, or compose it with another component. Accepts a `ReactElement` or a function that returns the element to render.

ComboboxItem

The ready-made option row: Base UI's `Item` behavior grafted onto the shared menu-row chrome so the combobox list matches the dropdown panel. `selection="check"` reserves a leading check gutter (kept mounted while unselected); `selection="checkbox"` shows the checkbox box used by multi-select.

PropTypeDefaultDescription
label(required)stringOption label.
renderReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, ComboboxItemState>Allows you to replace the component's HTML element with a different tag, or compose it with another component. Accepts a `ReactElement` or a function that returns the element to render.
variant"neutral" | "accent" | "danger""neutral"Row look. `neutral` is the standard text hierarchy, `accent` marks the primary action, and `danger` marks a destructive one.
selection"checkbox" | "check"checkSelection marker. `check` is the leading accent check used by single-select; `checkbox` is the checkbox box used by multi-select (the Figma dropdown row).
iconReactNodeLeading element shown after the selection marker, e.g. `<Icon icon={MapPin} tint="secondary" />`.
trailingReactElement<unknown, string | JSXElementConstructor<any>>Content pinned at the row's inline end, e.g. a count.

ComboboxItemIndicator

Ready-made combobox item indicator: Base UI's selection behavior grafted onto the styled marker, with a default check when no children are given (defaults are a `components` concern). Mounted on every row by default — the `layout="indicator"` listbox row places children positionally, so the marker must occupy the leading column even while unselected (the styled marker hides its glyph off `data-selected`).

PropTypeDefaultDescription
renderReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, ComboboxItemIndicatorState>Allows you to replace the component's HTML element with a different tag, or compose it with another component. Accepts a `ReactElement` or a function that returns the element to render.

ComboboxEmpty

The ready-made empty-state row: Base UI's `Empty` behavior grafted onto the styled `elements/combobox` row. Pass the — localizable — no-matches message as children.

PropTypeDefaultDescription
renderReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, ComboboxEmptyState>Allows you to replace the component's HTML element with a different tag, or compose it with another component. Accepts a `ReactElement` or a function that returns the element to render.

ComboboxStatus

A polite live region inside the popup for async hints ("Searching…", "12 results") — Base UI's `Status` behavior grafted onto the shared muted listbox hint styling.

No component-specific props — accepts the standard HTML attributes for the element it renders.

ComboboxGroup

Groups related options under a `ComboboxGroupLabel`. Pass the group's `items` so the nested `ComboboxCollection` renders (and filters) just this group's options.

PropTypeDefaultDescription
renderReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, ComboboxGroupState>Allows you to replace the component's HTML element with a different tag, or compose it with another component. Accepts a `ReactElement` or a function that returns the element to render.

ComboboxGroupLabel

The muted heading naming a `ComboboxGroup` — Base UI's `GroupLabel` behavior (labels the group for assistive tech) grafted onto the shared styled listbox heading.

No component-specific props — accepts the standard HTML attributes for the element it renders.

ComboboxCollection

Renders the filtered items of the nearest `ComboboxGroup` (or the root) through a function child — the grouped counterpart of `ComboboxList`'s function child.

No component-specific props — accepts the standard HTML attributes for the element it renders.

ComboboxRow

A grid row wrapper for multi-column listbox layouts (pass `cols` on the root's `grid`).

PropTypeDefaultDescription
renderReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, ComboboxRowState>Allows you to replace the component's HTML element with a different tag, or compose it with another component. Accepts a `ReactElement` or a function that returns the element to render.