Skip to content
Site volume
Colourway

Components

Combobox

A text field over a filtered list, for long or searchable lists. The docs search runs on it.

  • Experimental
  • Reading layer
  • Client leaf
  • Since 0.9.0
  • Press check
Import from@maxgrits/overprint-react/combobox
On this page

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#

Look of this example
Live render of examples/combobox/basic.tsx

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 filter for your own rule.
  • Many values. multiple prints the choices as removable chips, and Backspace removes the last.
  • Waiting and nothing found. loading says "Searching…" while results arrive; emptyText says what was not found, with the query.
  • In place. inline prints the list under the field instead of in a popup, as the docs search does inside its dialog.
  • Short lists are a Select or a RadioGroup; 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#

The parts of examples/combobox/basic.tsx, numbered:
  1. BoxThe Input box around the field and its trigger.data-mk-part="box"
  2. FieldTyping filters the list.data-mk-part="input"
  3. TriggerOpens the full list without typing.data-mk-part="trigger"

Volume#

Reading layer: identical at every volume, popup included.

  • Quiet

    Docs, forms, legal and long reads. Flat inks, no art in motion.

  • Studio

    The site default. Art on its own plates, one display moment per view.

  • Press

    Hero moments, covers and campaigns. Halftone fields and the second ink.

  • Rave

    Launches, posters and the 404. Every ink, and the words still win.

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

Type three letters, pick one.

Don't

A select with 140 countries, scrolled by hand.

Scrolling a long native list on a phone.

Past about fifteen options, scanning is slower than typing. A combobox lets people jump straight to what they already know.

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 Field label or aria-label. The list portals with the field's scope.
KeysWhat happens
Arrow DownorArrow UpOpens the list and moves through the options
EnterChooses the highlighted option
EscapeCloses the list
TypingFilters the list; a live region counts the results
BackspaceWith multiple choices and an empty input: removes the last chip

The colour pairs it prints, with their contrast on both papers:

TextSurfaceRoleBoneNight
--mk-on-control--mk-controluinot in this looknot in this look
--mk-text--mk-surface-raiseduinot in this looknot in this look
--mk-text--mk-hoveruinot in this looknot in this look
--mk-text-2--mk-surface-raiseduinot in this looknot in this look

Process at Studio, normal contrast. Every look: the press check.

Props#

PropTypeDefaultDescription
items requiredreadonly T[] | readonly ComboboxGroup<T>[]noneA flat list or groups (`{ label, items }`).
itemToString((item: T) => string)noneThe words of an item (default: its `label`, else `String(item)`); client callers only.
filter((item: T, query: string) => boolean) | nullnoneDefault: case- and accent-insensitive "contains"; `null` keeps every item (results filtered elsewhere).
multiplebooleannone
valueT | T[] | nullnone
defaultValueT | T[] | nullnone
onValueChange((value: T | T[] | null) => void)none
inputValuestringnone
defaultInputValuestringnone
onInputValueChange((query: string) => void)none
openbooleannone
onOpenChange((open: boolean) => void)none
autoHighlightbooleannoneHighlights the first match while typing, so Enter picks it.
loadingbooleannonePrints "Searching…" in the list.
emptyText((query: string) => ReactNode)"No matches for “{q}”"The words of an empty list (default "No matches for “{q}”").
limitnumbernoneAt most this many items.
inlinebooleannoneThe 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.
copyPartial<ComboboxCopy>none
classNamestringnoneLands on the box.
styleCSSPropertiesnone
containerHTMLElement | nulldocument.bodyPortal container (default `document.body`).

Plus 301 React and DOM attributes passed through to the root element (className, ref, aria-*, event handlers and the rest). Generated from ComboboxProps in packages/overprint-react/src/reading/combobox/combobox.tsx.

Tokens#

Changelog#

Arrived in 0.9.0.

0.9.024 October 2026

  • MinorreactCombobox and ComboboxItem (a case- and accent-insensitive filter by default, groups, chips for multiple, "Searching…", an empty message, a live result count, and inline for a list printed in place), Popover with PopoverTrigger, PopoverContent and PopoverClose, Menu with its eleven parts (shortcuts as Kbd caps, checkbox and radio items, group labels, separators, submenus), and ToastProvider with useToast (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 entry
  • Patchreact, threeCombobox gives 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; the EmptyState and Popover descriptions print in the meta role (16 px, 500, so --mk-text-2 clears the APCA floor); SplashTitle's Quiet heading prints on its own paper plate and wraps inside its cell; the Toast viewport and specimen keep one column no wider than their box; ProofSheet's frame links draw the focus ring. Read the whole entry