Combobox
A filterable input that selects one or more options from a list in a popup.
Basic
Show codeHide code
import {
Combobox,
ComboboxContent,
ComboboxEmpty,
ComboboxInputGroup,
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 { ChevronsUpDown, X } from "lucide-react";
const REGIONS = ["us-central-1", "us-east-1", "eu-central-1", "ap-west-1"];
export default function BasicDemo() {
return (
<Field name="region">
<Combobox items={REGIONS}>
<FieldLabel magnitude="md" inset={false}>
Region
</FieldLabel>
<ComboboxInputGroup
magnitude="md"
placeholder="e.g. eu-central-1"
clear={
<IconButton
prominence="ghost"
tone="neutral"
magnitude="md"
aria-label="Clear region"
icon={<Icon icon={X} />}
/>
}
trigger={
<IconButton
prominence="ghost"
tone="neutral"
magnitude="md"
aria-label="Open region"
icon={<Icon icon={ChevronsUpDown} />}
/>
}
/>
<ComboboxContent>
<ComboboxEmpty>No matches</ComboboxEmpty>
<ComboboxList>
{(region: string) => (
<ComboboxItem key={region} value={region} magnitude="md" label={region} />
)}
</ComboboxList>
</ComboboxContent>
</Combobox>
</Field>
);
}Multiple
multiple swaps the input frame for ComboboxChips: each selected value becomes a removable chip ahead of the inline input.
us-east-1
eu-central-1
Show codeHide 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 magnitude="md" inset={false}>
Regions
</FieldLabel>
<ComboboxChips magnitude="md" placeholder="Add a region">
{(region: string) => (
<ComboboxChip
key={region}
label={region}
remove={
<IconButton
prominence="ghost"
tone="neutral"
magnitude="sm"
aria-label={`Remove ${region}`}
icon={<Icon icon={X} />}
/>
}
/>
)}
</ComboboxChips>
<ComboboxContent>
<ComboboxEmpty>No matches</ComboboxEmpty>
<ComboboxList>
{(region: string) => (
<ComboboxItem key={region} value={region} magnitude="md" label={region} />
)}
</ComboboxList>
</ComboboxContent>
</Combobox>
</Field>
);
}Grouped
Show codeHide 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 magnitude="md" inset={false}>
Region
</FieldLabel>
<ComboboxInputGroup magnitude="md" 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} magnitude="md" label={region} />
)}
</ComboboxCollection>
</ComboboxGroup>
)}
</ComboboxList>
</ComboboxContent>
</Combobox>
</Field>
);
}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.
Show codeHide 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 { ChevronsUpDown, 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 magnitude="md" inset={false}>
Reviewer
</FieldLabel>
<ComboboxInputGroup
magnitude="md"
placeholder="e.g. Priya"
clear={
<IconButton
prominence="ghost"
tone="neutral"
magnitude="md"
aria-label="Clear reviewer"
icon={<Icon icon={X} />}
/>
}
trigger={
<IconButton
prominence="ghost"
tone="neutral"
magnitude="md"
aria-label="Open reviewer"
icon={<Icon icon={ChevronsUpDown} />}
/>
}
/>
<ComboboxContent>
<ComboboxStatus>{status}</ComboboxStatus>
<ComboboxEmpty>{!pending && trimmed !== "" ? "No members match" : null}</ComboboxEmpty>
<ComboboxList>
{(member: string) => (
<ComboboxItem key={member} value={member} magnitude="md" label={member} />
)}
</ComboboxList>
</ComboboxContent>
</Combobox>
</Field>
);
}Installation
import {
Combobox,
ComboboxContent,
ComboboxEmpty,
ComboboxInputGroup,
ComboboxItem,
ComboboxList,
} from "@makeplane/propel/components/combobox";
Props
Combobox
| Prop | Type | Required | Description |
|---|---|---|---|
| autoComplete | string | No | Provides a hint to the browser for autofill. @see https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Attributes/autocomplete |
| autoHighlight | boolean | No | Whether the first matching item is highlighted automatically while filtering. |
| highlightItemOnHover | boolean | No | Whether 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) | No | 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) | No | 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) | No | 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. |
| actionsRef | RefObject<Actions | null> | No | 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) | No | Event handler called when the popup is opened or closed. |
| onInputValueChange | ((inputValue: string, eventDetails: ChangeEventDetails) => void) | No | Event handler called when the input value changes. |
| onItemHighlighted | ((highlightedValue: Value, eventDetails: HighlightEventDetails) => void) | No | 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. |
| multiple | boolean | No | Whether multiple items can be selected. |
| defaultValue | ComboboxValueType<Value, Multiple> | null | No | The uncontrolled selected value of the combobox when it's initially rendered. To render a controlled combobox, use the `value` prop instead. |
| value | ComboboxValueType<Value, Multiple> | null | No | The selected value of the combobox. Use when controlled. |
| onValueChange | ((value: ComboboxValueType<Value, Multiple> | (Multiple extends true ? never : null), eventDetails: ChangeEventDetails) => void) | No | Event handler called when the selected value of the combobox changes. |
ComboboxInputGroup
| Prop | Type | Required | Description |
|---|---|---|---|
| magnitude | "sm" | "md" | "lg" | "xl" | Yes | Visual size of the input row: height, text, and glyph sizing. Required. |
| icon | ReactNode | No | Decorative leading element at the inline-start, e.g. `<Icon icon={Search} tint="placeholder" />`. |
| clear | ReactElement<unknown, string | JSXElementConstructor<any>> | No | 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. |
| trigger | ReactElement<unknown, string | JSXElementConstructor<any>> | No | 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. |
ComboboxItem
| Prop | Type | Required | Description |
|---|---|---|---|
| render | ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, ComboboxItemState> | No | 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. |
| magnitude | "sm" | "md" | "lg" | Yes | Row height and text size. Required. |
| label | string | Yes | Option label. |