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 region, a warehouse size, a schedule.
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 a menu, whose rows are actions in mono capitals.

Rules

10 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 "eu-west-2, 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, ten rows, and has no search or loading state to reach the rest.
Never
Draw a ring, an outline or a shadow on SelectTrigger to show focus or the open popup. Because the trigger is a field, and a field shows focus itself, white with the gold edge doubled inward and a 1px pale halo; a ring outside that reads as a second box.
Never
Pad an option or a label in className, or move the check to the start. Because an option's text starts on the trigger's own text inset, 11.5px from the edge at 32px, so the value you chose and the list line up; a leading check column pushes every option 26px past the trigger's text.
Never
Round the trigger or set its height in className; pass size. Because every corner in Sodium is square, and a fixed height clips the value at 200% zoom.
Should
Pass size="sm" to SelectContent when the trigger is size="sm", so 26px 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 lifts to white between two --line hairlines, the focus indicator inside the popup, text-1 on it 18.3:1 and 13:1; forced colors draw a 2px outline on it.
The chosen row has an ink check as well as aria-selected and no fill, so it never rests on colour.
Focus or the open popup turn the trigger white with its edge --focus at 2px, 6.2:1 in light and 12.9:1 in dark, and a pale halo; 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 value
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 with the text.
Zoom 200%
The field grows past its token with the text.
Inside a Dialog
The popup portals above it; Escape closes the popup only.
Disabled Field
The trigger drops to the well 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

32 tokens
--field-bg
#f8f7f3
--field-hover
#fefefd
--field-edge-hover
#ecebe5
--field-ring-hover
#f4f3ee
--field-focus-bg
#ffffff
--field-ring-focus
#fff1b8
--field-ring-fail
#fbdcd8
--field-inset-sm
7px
--field-inset
10px
--field-inset-lg
11px
--row-highlight
#ffffff
--row-highlight-edge
var(--line)
--panel
#fafaf7
--well
#f4f3ee
--line
#e2dfd5
--focus
#7a5d00
--fail
#b3261e
--text-1
#161512
--text-3
#6b675e
--text-disabled
#a8a499
--control-sm
26px
--control
32px
--control-lg
36px
--row-sm
32px
--menu-min
192px
--popup-max
320px
--popover
288px
--type-body-sm
400 14px/20px var(--font-sans)
--type-caption
400 13px/18px var(--font-sans)
--type-micro
400 11px/14px var(--font-mono)
--icon
16px
--icon-sm
14px

Install

For your coding agent

GitHub

Please star me pookie

198

A new design system every week

Leave your email before you copy the prompt.