Select

Picks one option from a list.

Sizes

  • sm

  • defaultdefault

  • lg

Popup

  • defaultdefault

  • sm

Native

  • falsedefault

  • true

States

  • rest

  • placeholder

  • hover

  • focus

  • invalid

  • disabled

Usage

When to use it
Use
One choice from about five to thirty options known up front: a model, a region, a reply language.
Use
A form on phones or one built from a schema (native).
Not for
Two to five options that fit on screen. Use Radio group.
Not for
More than thirty options, or the person types the name. Use Combobox.
Not for
The choice switches a view on this page. Use Segmented control.
Not for
A list of actions, not a value. Use Dropdown menu.

Rules

9 rules
Must
Pass the options as items on Select as well as rendering SelectItems. Because Base UI reads the chosen label from items, so without it the trigger shows the raw value until the popup first opens.
Must
Name the trigger with a Field and FieldLabel, or aria-label; its text is the value, not the question. Because a screen reader hears "glaze-large, combobox" with no question.
Must
Say what to pick with SelectValue placeholder; never add a None option. Because a None row counts as a real value, so a required field passes empty and the list gains a fake row.
Must
Use Combobox past thirty options or for options that load. Because the popup stops at 320px, about nine rows, and has no search or loading state to reach the rest.
Never
Draw a ring, a shadow or a black line on SelectTrigger to show focus or the open popup. Because the trigger is a field, and a field shows focus by darkening its own line; a ring makes it read as a button.
Never
Square off the trigger or set its height in className; pass size. Because every one-line control is a pill, and a fixed height clips the value at 200% zoom.
Should
Pass size="sm" to SelectContent when the trigger is size="sm", so 28px rows match the 26px trigger.
Should
Use native for a form used mostly on phones or generated from a schema.
Should
Set aria-invalid on the trigger after a submit and say why in the FieldError.

State order

6 states, highest first
1
disabled
2
invalid
3
open
4
focus
5
hover
6
rest

Accessibility

5 checks
The trigger is a combobox with aria-haspopup="listbox", aria-expanded and aria-invalid; the popup is a listbox of options with aria-selected.
The highlighted row's wash is the focus indicator inside the popup; forced colors draw a 2px outline on it.
The chosen row has a check as well as aria-selected, so it never rests on colour.
Focus darkens the trigger's line to --line-focus (2.3:1 on paper); the caret and the value stay.
native keeps the platform picker, screen reader and keyboard, and sets 16px text under a coarse pointer.

Keyboard

4 keys
Enter, Space, Down, Up
On the trigger: opens with the chosen row highlighted.
A letter
Jumps to the next option starting with it, open or closed.
Enter
Picks the highlighted row and returns focus to the trigger.
Escape
Closes without a change.

Edge cases

7 edge cases
Long label
Truncates with an ellipsis in the trigger and the row; the popup stops at --popover wide.
Forty options
The list scrolls inside 320px between scroll arrows; use a Combobox.
Near the viewport bottom
The popup flips above the trigger and stays 8px inside.
Right to left
The check and the caret swap sides.
Zoom 200%
The pill grows past its token with the text.
Inside a Dialog
The popup portals above it; Escape closes the popup only.
Disabled Field
The trigger goes grey with the Field.

API

9 props
Select items
{value, label, disabled?}[] | Record
Select value, defaultValue, onValueChange
string | null
Select native, size, placeholder
boolean, 'sm' | 'default' | 'lg', string default false, 'default'
SelectTrigger size
'sm' | 'default' | 'lg' default 'default'
SelectTrigger aria-invalid
boolean default false
SelectValue placeholder
ReactNode
SelectContent size
'default' | 'sm' default 'default'
SelectContent side, align, alignItemWithTrigger
Base UI default 'bottom', 'start', false
SelectGroup, SelectLabel, SelectSeparator
Base UI

Tokens

25 tokens
--paper
#ffffff
--field
#efefef
--line-control
rgb(5 5 5 / 0.15)
--line-focus
rgb(5 5 5 / 0.32)
--line-faint
rgb(5 5 5 / 0.06)
--line
rgb(5 5 5 / 0.10)
--fail
#e35e49
--flat-hover
#f8f8f8
--hover
rgb(5 5 5 / 0.06)
--text-1
rgb(5 5 5 / 0.80)
--text-3
rgb(5 5 5 / 0.45)
--text-disabled
rgb(5 5 5 / 0.32)
--control-sm
26px
--control
32px
--control-lg
36px
--menu-row
32px
--menu-row-sm
28px
--menu-min
192px
--popup-max
320px
--popover
288px
--radius-pill
999px
--radius-overlay
10px
--radius-tag
6px
--type-body-sm
400 13px/1.5 var(--font-sans)
--type-label
500 13px/1.35 var(--font-sans)

Install

For your coding agent

GitHub

Please star me pookie

141

A new design system every week

Leave your email before you copy the prompt.