Combobox
A closed-field picker that selects one or more options from a searchable overlay panel — the same shell the dropdown menu uses.
Show codeHide code
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 codeHide code
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 codeHide code
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 codeHide code
Grouped
Show codeHide code
Async search
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 codeHide code
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 codeHide code
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.
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`).
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).
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.
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`.
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.
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.
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.
ComboboxStickyList
Pads sticky header rows (Select all + separator) with the same inset the scrolling list uses.
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.
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).
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.
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`).
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.
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.
ComboboxGroup
Groups related options under a `ComboboxGroupLabel`. Pass the group's `items` so the nested `ComboboxCollection` renders (and filters) just this group's options.
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.
ComboboxCollection
Renders the filtered items of the nearest `ComboboxGroup` (or the root) through a function child — the grouped counterpart of `ComboboxList`'s function child.
ComboboxRow
A grid row wrapper for multi-column listbox layouts (pass `cols` on the root's `grid`).