Combobox · Combobox menu
Combobox — a Select you can type into: the trigger opens a list with a search field, the list narrows as you type, one row is chosen.
Beta · Forms · packages/css/src/components/combobox.css
.pui-combobox (Select’s trigger with chevrons-up-down and an <input> as __value) · .pui-combobox-menu (__search __items __empty, rows are .pui-select-item).
Sizes across, states down.
What it is for
Section titled “What it is for”- Placeholder and Value, Has placeholder / Has value — the value is the chosen row’s label, or the query while typing
- Has prefix + Prefix, Has icon leading + Icon leading — Select’s, unchanged
- Three sizes 28 / 36 / 44, five states — Select’s exactly; open is the focus state with the menu under it (the example)
- The list is Combobox menu — the search row, the rows, the empty row
Use when one choice from a list long enough to search — accounts, offers, people, countries — roughly twenty or more, or any list the user knows by name.
Do not use when the list is short (Select); the user may type a value that is not in the list (Input, with suggestions later); several may be chosen (a multi-select Combobox is a later row); the control runs actions (Dropdown menu).
Relates to Select, Select menu, Select item, Input, Field, Combobox menu, Dropdown menu.
Anti-patterns a combobox with five options · a search field that filters nothing (that is Input group) · a value that is not one of the rows · a chevron-down on it (that says Select) · a fill changed on the instance.
Keywords: combobox, autocomplete, searchable select, typeahead, react-select
Figma — page Combobox: Combobox (15 variants) · Combobox menu (1 variants)
shadcn: combobox
The markup — copy this
Section titled “The markup — copy this”The class, the parts, and nothing that is not in the package. The same block opens combobox.css.
<div class="pui-combobox"> <input class="pui-combobox__value" role="combobox" aria-expanded="false" aria-controls="menu-id" aria-autocomplete="list" aria-label="Account" placeholder="Pick an account"> (ARIA 1.2: the role, the expanded state and the name sit on the input, not on the box, axe) <span class="pui-icon pui-combobox__chevron">…chevrons-up-down…</span></div><div class="pui-combobox-menu"> <div class="pui-combobox-menu__search"><span class="pui-icon">…search…</span><input aria-label="Search accounts" placeholder="Search…"></div> <div class="pui-combobox-menu__items" role="listbox"> …pui-select-item… </div> <div class="pui-combobox-menu__empty">No results.</div></div>Rule — one row highlighted at a time — the first visible one when the list opens (data-highlighted).
Accessibility — measured, not assumed
Section titled “Accessibility — measured, not assumed”Every number here is measured on the rendered component: contrast (npm run contrast:render), the focus ring (npm run focus) and the keyboard (npm run keyboard). The floors are principle 12’s.
Contrast
Section titled “Contrast”| ink on ground | reads | floor | the matrix calls it | |
|---|---|---|---|---|
#544d4d on #ffffff |
8.24 | 4.5 | text.muted on surface.page (promised) | ok |
#0b0202 on #f2f2f2 |
18.32 | 4.5 | text.default on surface.sunken (promised) | ok |
#0b0202 on #ffffff |
20.51 | 4.5 | text.default on surface.page (promised) | ok |
3 distinct pair(s) rendered, 0 that axe calls a violation.
The focus ring
Section titled “The focus ring”| primitive | on | the indicator | over | the change | |
|---|---|---|---|---|---|
| Combobox menu search | surface/page | shadow-inset #6a66e4 2 px |
#ffffff |
4.53 | ok |
4 of 4 focus stop(s) in this block meet WCAG 2.4.13 on screen — the same crop screenshotted focused and unfocused, and the difference measured; 1 stop(s) the Tab key does not reach or that the specimen draws in a state that only exists while focused.
Keyboard
Section titled “Keyboard”| role | tab stops | what the pattern requires | |
|---|---|---|---|
listbox .pui-combobox-menu__items |
0 | 0 — its trigger holds it | ok |
combobox .pui-combobox__value |
0 | — | — |
combobox .pui-combobox__value |
0 | — | — |
combobox .pui-combobox__value |
0 | — | — |
What the consumer’s script owes. the input holds the tab stop and carries aria-expanded, aria-controls and, while open, aria-activedescendant; every option in the popup takes tabindex=“-1”. The script owes Down to open and move, Up / Down inside, Enter to select, Escape to close then clear.
Tab order across the whole library is document order over 344 stops, and Shift+Tab is its exact reverse — no keyboard trap anywhere (WCAG 2.1.2).
Pepper UI v1.5.0
The Mobupps design system. 386 tokens, 88 components, 65 pages in the workshop — every page of this site generated from the repository, so nothing here is typed twice.