Select
StablePicks one value from a short, known list in a popup. The trigger looks like an Input so forms read as one family; the list supports groups, descriptions and disabled options.
When to use
Use it for
- One choice from about 4 to 10 known options: a theme, a brand, a token group, a role.
- The options are long words or labels that would crowd a RadioGroup.
- Space is tight: in a toolbar, a table filter or a dense settings row (size sm).
Not for
- Two or three options that should all stay visible: use RadioGroup, or an attached ButtonGroup for a view switch.
- Long or unknown lists (people, repositories, 20+ tokens): use Combobox so users can type to filter.
- Several values at once: use Combobox with multiple, or a CheckboxGroup.
- Actions (Duplicate, Delete): use Menu. A Select changes a value, it does not run commands.
Do and don't
Every example is rendered with the real component, so it always matches what ships.
Order options by meaning, not alphabetically by default
Put the most common choice first, or follow the natural order (System, Light, Dark). Group long lists under short headings.
Explain a disabled option
A description on the option says why it cannot be picked, instead of leaving users guessing.
A Select with two options
Hiding two choices behind a click costs a step for nothing. Show them as radios or a Switch.
The placeholder as an instruction
"Click to select" says nothing. The placeholder describes the empty state: "No theme", "Any reviewer".
Accessibility
- Base UI Select: the trigger is a combobox button with aria-expanded; the popup is a listbox with options marked aria-selected and aria-disabled.
- Keyboard: Enter, Space or Arrow keys open it; arrows move; typing jumps to a matching option; Enter selects; Escape closes and returns focus to the trigger.
- The Field label names the trigger; hint and error are linked through aria-describedby, and an error sets aria-invalid.
- The selected option shows a check mark and a medium weight, not color alone.
- The trigger edge uses color.border.strong (3:1 against surfaces) and the signal focus ring on keyboard focus.
- The popup is portaled and re-applies the theme, brand and density of its trigger.
Anatomy and tokens
Read from packages/react/src/components/Select/Select.module.css at build time. Every value is a token: open one to see its
alias chain in both themes.
- Trigger box (same edge and focus as Input)
.trigger - sm
.sm - lg
.lg - Trigger box (same edge and focus as Input), hover
@media (hover: hover) .trigger:hover:not([data-disabled], [aria-invalid="true"], [data-invalid]) - Trigger box (same edge and focus as Input), focus
.trigger:focus-visible, .trigger[data-popup-open] - Trigger box (same edge and focus as Input)
.trigger[data-invalid], .trigger[aria-invalid="true"] - Trigger box (same edge and focus as Input), focus
.trigger[data-invalid]:focus-visible, .trigger[aria-invalid="true"]:focus-visible - Trigger box (same edge and focus as Input)
.trigger[data-disabled] - Trigger box (same edge and focus as Input)
.trigger[data-readonly] - Selected value or placeholder
.value[data-placeholder] - Trigger box (same edge and focus as Input)
.trigger[data-disabled] .value[data-placeholder] - Prefix icon
.affix - Chevron, turns when open
.chevron - positioner
.positioner - Popup surface (popup.* tokens)
.popup - Popup surface (popup.* tokens)
.popup[data-ending-style] - Scrollable list
.list - Option
.item - Option
.item[data-highlighted] - Option
.item[data-selected] .itemText - Option
.item[data-disabled] - Option description
.itemDescription - Option
.item[data-disabled] .itemDescription - Check mark on the selected option
.indicator - Group heading
.groupLabel - Divider between groups
.separator - Trigger box (same edge and focus as Input)
@media (pointer: coarse) .trigger
Props
Parsed from the TypeScript types in packages/react/src/components/Select/Select.tsx, so this table cannot drift from the
code.
It also accepts every native <button> attribute except value, defaultValue, onChange, prefix, children.
| Prop | Type | Default | Description |
|---|---|---|---|
options required | SelectOption[] | none | The choices. Keep them under about ten; use Combobox for long or searchable lists. |
placeholder | string | 'Select an option' | Text shown when nothing is selected. Describe the choice, not the action: "No theme". |
value | string | null | none | Selected value (controlled). `null` means nothing selected. |
defaultValue | string | null | none | Initially selected value (uncontrolled). |
onValueChange | (value: string | null) => void | none | Called with the new value when the user picks an option. |
label | string | none | Visible label. When set, the Select wraps itself in a Field. Leave unset inside a Field. |
hint | ReactNode | none | Help under the label (Field description). Only with `label`. |
error | ReactNode | none | Error message. Marks the select invalid and announces the message. Only with `label`. |
hideLabel | boolean | none | Visually hide the Field label (it stays the accessible name). |
required | boolean | none | No description in the code yet. |
name | string | none | Name for form submission (a hidden input carries the value). |
size | 'sm' | 'md' | 'lg' | 'md' | Height follows the density tokens. Default `md`. |
prefix | ReactNode | none | Content before the value, usually an Icon. |
open | boolean | none | Popup open state (controlled). |
defaultOpen | boolean | none | Open on first render (uncontrolled). |
onOpenChange | (open: boolean) => void | none | No description in the code yet. |
Playground
Change the props and see the real Select update. Controls are generated from the TypeScript props; nothing here changes the system.
<Select
placeholder="No theme"
label="Theme"
defaultValue="dark"
/>Workbench
For contributors: every state with the accessibility panel, in the component workbench (Storybook). The stories double as visual test fixtures; the guidance on this page is the user documentation (open Select in Storybook) .
Embed Storybook stories here (6)
Loads from https://bridge-storybook.floriangouloubi.com (set STORYBOOK_URL at build time). If Storybook is not running, the frames stay
empty.
Default
WithValue
WithError
Grouped
Open
AllStates
Changelog
- New
Forms: Field (label, description, error wired by Base UI Field) and Input rebuilt on it (prefix, suffix, sizes; label, hint and error props kept). New Textarea (autosize, character count), Select (groups, descriptions), Combobox (filtering, async loading and empty states, multiple with chips), Checkbox (indeterminate, parent), CheckboxGroup, RadioGroup, Switch, Slider (single and range), NumberField (steppers, units) and Form (server errors with a focusable error summary, FormActions footer).
Unreleased (current version 0.1.0, no release tagged yet) , Oct 17, 2026