Combobox is a text field over a list: typing filters it (case and accents ignored), the arrows move through it and Enter picks. The field wears the kit's Input box; the list is the menu plate with 44 px rows. multiple prints the choices as removable chips; loading says "Searching…"; an empty list says what was not found. inline prints the list in place under the field, as the docs search does in its dialog.
Example#
Usage#
Use a combobox for a long list someone searches rather than scans: people, projects, tags, the docs search. Typing filters the list; the arrows move through it and Enter picks.
- Filtering ignores case and accents by default; pass
filterfor your own rule. - Many values.
multipleprints the choices as removable chips, and Backspace removes the last. - Waiting and nothing found.
loadingsays "Searching…" while results arrive;emptyTextsays what was not found, with the query. - In place.
inlineprints the list under the field instead of in a popup, as the docs search does inside its dialog. - Short lists are a
Selector aRadioGroup; a combobox only earns its keystrokes past about fifteen options.
Recipe: reviewers in groups, several at once#
"use client";
import { Combobox } from "@maxgrits/overprint-react/combobox";
const PEOPLE = [
{ label: "Studio", items: [{ label: "Jordan Reyes", description: "Design" }, { label: "Sam Okafor", description: "Build" }] },
{ label: "Clients", items: [{ label: "Dana Whitfield", description: "Fernlight" }, { label: "Mei Tanaka", description: "Pebblepath" }] },
];
export function Reviewers() {
return (
<Combobox
items={PEOPLE}
multiple
itemToString={(p) => p.label}
aria-label="Reviewers"
placeholder="Add a reviewer"
emptyText="No one by that name yet"
/>
);
}Groups print their labels as list headings; with multiple, each pick becomes a chip in the field, and Backspace removes the last one. Items with a description show it under the name, so a server component can pass plain objects.
Anatomy#
- BoxThe Input box around the field and its trigger.
data-mk-part="box" - FieldTyping filters the list.
data-mk-part="input" - TriggerOpens the full list without typing.
data-mk-part="trigger"
Volume#
Reading layer: identical at every volume, popup included.
Quiet
Studio
Press
Rave
Content#
- The placeholder says what can be found: "Find a person", "Search the docs".
- The empty state repeats the query and offers a way out: "No matches for "pebl". Check the spelling or browse all projects."
- Items lead with the words people type; a description line can add the detail that tells two similar items apart.
Do and don't#
Do
Don't
A select with 140 countries, scrolled by hand.
Accessibility#
- The APG combobox through Base UI: Down and Up open and move, Enter selects, Escape closes, typing filters.
- A polite live region counts the results.
- Name it with a
Fieldlabel oraria-label. The list portals with the field's scope.
| Keys | What happens |
|---|---|
| Arrow DownorArrow Up | Opens the list and moves through the options |
| Enter | Chooses the highlighted option |
| Escape | Closes the list |
| Typing | Filters the list; a live region counts the results |
| Backspace | With multiple choices and an empty input: removes the last chip |
The colour pairs it prints, with their contrast on both papers:
| Text | Surface | Role | Bone | Night |
|---|---|---|---|---|
--mk-on-control | --mk-control | ui | not in this look | not in this look |
--mk-text | --mk-surface-raised | ui | not in this look | not in this look |
--mk-text | --mk-hover | ui | not in this look | not in this look |
--mk-text-2 | --mk-surface-raised | ui | not in this look | not in this look |
Props#
| Prop | Type | Default | Description |
|---|---|---|---|
items required | readonly T[] | readonly ComboboxGroup<T>[] | none | A flat list or groups (`{ label, items }`). |
itemToString | ((item: T) => string) | none | The words of an item (default: its `label`, else `String(item)`); client callers only. |
filter | ((item: T, query: string) => boolean) | null | none | Default: case- and accent-insensitive "contains"; `null` keeps every item (results filtered elsewhere). |
multiple | boolean | none | |
value | T | T[] | null | none | |
defaultValue | T | T[] | null | none | |
onValueChange | ((value: T | T[] | null) => void) | none | |
inputValue | string | none | |
defaultInputValue | string | none | |
onInputValueChange | ((query: string) => void) | none | |
open | boolean | none | |
onOpenChange | ((open: boolean) => void) | none | |
autoHighlight | boolean | none | Highlights the first match while typing, so Enter picks it. |
loading | boolean | none | Prints "Searching…" in the list. |
emptyText | ((query: string) => ReactNode) | "No matches for “{q}”" | The words of an empty list (default "No matches for “{q}”"). |
limit | number | none | At most this many items. |
inline | boolean | none | The list prints in place under the input, always open (a search palette inside a Dialog). |
children | ((item: T) => ReactNode) | <ComboboxItem> | Renders one item (client callers); default `<ComboboxItem>` with `itemToString(item)` and the item's `description`, so a server component can pass `{ label, description }` items without a function. |
copy | Partial<ComboboxCopy> | none | |
className | string | none | Lands on the box. |
style | CSSProperties | none | |
container | HTMLElement | null | document.body | Portal container (default `document.body`). |
Tokens#
System tokens (34)
--mk-border--mk-border-thick--mk-control--mk-control-h-md--mk-control-rule--mk-dur-menu--mk-dur-reduced--mk-ease-out--mk-focus--mk-font-text--mk-hover--mk-on-control--mk-on-control-2--mk-radius-control--mk-radius-plate--mk-rule--mk-rule-soft--mk-shadow-menu--mk-space-1--mk-space-2--mk-space-3--mk-surface-raised--mk-text--mk-text-2--mk-text-disabled--mk-type-body-ui-leading--mk-type-body-ui-size--mk-type-body-ui-weight--mk-type-meta-leading--mk-type-meta-size--mk-type-meta-weight--mk-type-row-title-leading--mk-type-row-title-size--mk-z-popover
Its own properties (4)
--mk-combobox-chip-end--mk-combobox-line-gap--mk-combobox-pad--mk-combobox-popup-pad
Changelog#
Arrived in 0.9.0.
ComboboxandComboboxItem(a case- and accent-insensitive filter by default, groups, chips formultiple, "Searching…", an empty message, a live result count, andinlinefor a list printed in place),PopoverwithPopoverTrigger,PopoverContentandPopoverClose,Menuwith its eleven parts (shortcuts asKbdcaps, checkbox and radio items, group labels, separators, submenus), andToastProviderwithuseToast(a slab pill, one at a time with three waiting, 5.2 s with a drain bar that holds while the region is hovered, focused or the window is away). Read the whole entryComboboxgives its inline list an id and the input's name in the server markup and marks its box as the input's focus-ring host, and a highlighted row's second line prints in--mk-text; theEmptyStateandPopoverdescriptions print in the meta role (16 px, 500, so--mk-text-2clears the APCA floor);SplashTitle's Quiet heading prints on its own paper plate and wraps inside its cell; theToastviewport and specimen keep one column no wider than their box;ProofSheet's frame links draw the focus ring. Read the whole entry