Select · Select menu · Select item
Select — one choice from a list that opens on demand.
Beta · Forms · packages/css/src/components/select.css · React on Base UI: @pepperui/select
.pui-select (a button; __prefix __value __placeholder __icon __chevron, sizes, aria-expanded, aria-invalid) · .pui-select-menu (__label __separator) · .pui-select-item (aria-selected, data-highlighted, __icon __check).
Sizes across, states down.
What it is for
Section titled “What it is for”- Placeholder and Value are separate text properties — toggle Has placeholder / Has value
- Has prefix + Prefix: a short label inside the control (“Sort:”) when the field label is elsewhere
- Has icon leading + Icon leading: a 16px leading glyph
- Three sizes mirroring Input (28 / 36 / 44); five states: default, hover, focus, error, disabled — the same rules as Input
- The open list is Select menu, composed of Select items; the example on this page shows the pair
- Filled true — the box on surface/card instead of white, so a form on a white page can tell its dropdowns from its inputs: The design lead’s call on the site’s blog and careers filters, ‘for visibility’ (request 19)
Use when one option from a list too long to show as radios — about seven or more — or when space is tight.
Do not use when the list needs typing or search (Combobox); the options are few and should all be visible (Radio, Toggle group); more than one may be chosen (a Checkbox list or a multi-select Combobox); the control runs an action (Dropdown menu).
Relates to Select menu, Select item, Combobox, Input (same chrome), Field (parent), Dropdown menu, Native select.
Anti-patterns a select with two options (Radio or Switch) · a placeholder that reads like a value · a select used for navigation · a prefix that repeats the field label.
Keywords: select, dropdown, picker, choose, options, combobox
Figma — page Select: Select (30 variants) · Select item (6 variants) · Select menu (1 variants)
shadcn: select
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 select.css.
<button class="pui-select" type="button" aria-haspopup="listbox" aria-expanded="false"> <span class="pui-select__prefix">Sort:</span> <span class="pui-select__value pui-select__placeholder">Choose…</span> <span class="pui-icon pui-select__chevron">…chevron-down…</span></button><div class="pui-select-menu" role="listbox"> <div class="pui-select-menu__label">Recent</div> <div class="pui-select-item" role="option" aria-selected="true">Newest first <span class="pui-icon pui-select-item__check">…check…</span></div> <div class="pui-select-menu__separator" role="separator"></div> …</div>The modifiers
Section titled “The modifiers”| What it is | Note | |
|---|---|---|
| Trigger | Size --small 28 · (none) medium 36 · --large 44; states :hover · :focus-visible / [aria-expanded=“true”] · error = [aria-invalid=“true”], --error · :disabled; parts __icon (leading), __prefix, __value, __placeholder (the caption ink), __chevron | — |
| Menu | __label (12, text/subtle) · __separator (1 px border/default, 4 above and below) · items | — |
| Item | selected = [aria-selected=“true”] shows __check · highlighted = :hover, :focus, [data-highlighted]; keyboard focus adds the inset ring on top of the shared surface (as Menu item) (keyboard and pointer share the surface) · [aria-disabled=“true”] .5; parts __icon, __check | — |
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 | |
|---|---|---|---|---|---|
| Select | surface/page | shadow #6a66e4 2 px |
#ffffff |
4.53 | ok |
| Select | surface/card | shadow #6a66e4 2 px |
#f2f2f7 |
4.06 | ok |
| Select | surface/sunken | shadow #6a66e4 2 px |
#f2f2f2 |
4.05 | ok |
| Combobox | surface/page | shadow #6a66e4 2 px |
#ffffff |
4.53 | ok |
| Combobox | surface/card | shadow #6a66e4 2 px |
#f2f2f7 |
4.06 | ok |
| Combobox | surface/sunken | shadow #6a66e4 2 px |
#f2f2f2 |
4.05 | ok |
| Select item | 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-select-menu |
0 | 0 — its trigger holds it | ok |
What the consumer’s script owes. the trigger is the tab stop; the menu takes tabindex=“-1” and so does every item. The script owes Up / Down, Home / End, Enter / Space to select, Escape to close, and typeahead. Combobox is the same list with the INPUT holding the tab stop and naming the active option with aria-activedescendant.
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.