Combobox
The Combobox component provides a searchable combobox with built-in filtering. Pass items and get a ready-to-use combobox out of the box. For async data fetching use Combobox.Async. For user-created items add an onCreateItem prop. For custom compositions use Combobox.Parts.
Import
import { Combobox } from "@tailor-platform/app-shell";Basic Usage
<Combobox
items={["Apple", "Banana", "Cherry"]}
placeholder="Search fruits..."
onValueChange={(value) => console.log(value)}
/>Props
Combobox Props
| Prop | Type | Default | Description |
|---|---|---|---|
items | I[] | - | Items to display. May be a flat array or an array of ItemGroup<T> |
placeholder | string | - | Placeholder text for the input |
emptyText | string | "No results." | Text shown when no items match |
multiple | true | false | undefined | false | Enables multi-select mode |
value | T | null (single) or T[] (multiple) | - | Controlled value |
defaultValue | T | null (single) or T[] (multiple) | - | Initial value (uncontrolled) |
onValueChange | (value: T | null) => void (single) or (value: T[]) => void (multiple) | - | Called when the selected value changes |
mapItem | (item: T) => MappedItem | - | Map each item to its label, key, and optional custom render |
className | string | - | Additional CSS classes for the root container |
disabled | boolean | false | Disables the combobox |
onCreateItem | (value: string) => T | false | Promise<T | false> | - | Enable user-created items (requires mapItem; T must be an object type) |
formatCreateLabel | (value: string) => string | (v) => `Create "${v}"` | Format the label for the "create" option |
MappedItem
interface MappedItem {
label: string; // Display text, used for filtering and a11y
key?: string; // React key. Defaults to label
render?: React.ReactNode; // Custom JSX to render in the dropdown
}ItemGroup
interface ItemGroup<T> {
label: string;
items: T[];
}Grouped Items
const fruits = [
{ label: "Citrus", items: ["Orange", "Lemon", "Lime"] },
{ label: "Berries", items: ["Strawberry", "Blueberry"] },
];
<Combobox items={fruits} placeholder="Search fruits..." />;Multi-select
In multi-select mode, selected items are displayed as chips inside the input:
<Combobox
items={["Red", "Green", "Blue"]}
multiple
placeholder="Pick colors"
onValueChange={(colors) => console.log(colors)}
/>Creatable Items
Add onCreateItem to let users create new items on-the-fly. T must be an object type:
type Tag = { id: string; name: string };
const [tags, setTags] = useState<Tag[]>([
{ id: "1", name: "Bug" },
{ id: "2", name: "Feature" },
]);
<Combobox
items={tags}
mapItem={(tag) => ({ label: tag.name, key: tag.id })}
onCreateItem={(value) => {
const newTag: Tag = { id: crypto.randomUUID(), name: value };
setTags((prev) => [...prev, newTag]);
return newTag; // return the new item to add it to the selection
}}
placeholder="Search or create a tag..."
/>;onCreateItem may return:
T— accept the item and add it to the selectionfalse— cancel the creationPromise<T | false>— for async workflows
Async Loading
Use Combobox.Async to load items from an API. The fetcher is called on each keystroke (debounced).
import { type ComboboxAsyncFetcher } from "@tailor-platform/app-shell";
const fetcher: ComboboxAsyncFetcher<User> = async (query, { signal }) => {
const res = await fetch(`/api/users?q=${query}`, { signal });
return res.json();
};
<Combobox.Async
fetcher={fetcher}
mapItem={(user) => ({ label: user.name, key: user.id })}
placeholder="Search users..."
onValueChange={(user) => console.log(user)}
/>;Combobox.Async also supports onCreateItem for creatable async comboboxes.
Combobox.Async Props
Accepts all the same props as Combobox except items, plus:
| Prop | Type | Default | Description |
|---|---|---|---|
fetcher | ComboboxAsyncFetcher<T> | - | Fetcher called on each keystroke (debounced by default) |
loadingText | string | "Loading..." | Text shown while loading |
ComboboxAsyncFetcher
type ComboboxAsyncFetcher<T> =
| ((query: string, options: { signal: AbortSignal }) => Promise<T[]>)
| { fn: (query: string, options: { signal: AbortSignal }) => Promise<T[]>; debounceMs: number };Pass { fn, debounceMs } to customize the debounce delay. Errors thrown by the fetcher are silently caught — handle errors inside the fetcher.
Low-level Primitives
Combobox.Parts exposes styled sub-components and hooks for fully custom compositions:
const {
Root,
InputGroup,
Input,
Trigger,
Content,
List,
Item,
Empty,
Group,
GroupLabel,
Clear,
Chips,
Chip,
ChipRemove,
Value,
Collection,
Status,
useFilter,
useCreatable,
useAsync,
} = Combobox.Parts;Examples
Controlled Combobox
const [selected, setSelected] = useState<User | null>(null);
<Combobox
items={users}
mapItem={(u) => ({ label: u.name, key: u.id })}
value={selected}
onValueChange={setSelected}
placeholder="Select a user"
/>;Accessibility
- Input is keyboard accessible with arrow key navigation
- Pressing
Escapecloses the dropdown - Multi-select chips have
aria-labelset from the item label
Related Components
- Select - Non-searchable dropdown
- Autocomplete - Free-text input with suggestions