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?}[] | RecordSelect value, defaultValue, onValueChangestring | nullSelect native, size, placeholderboolean, 'sm' | 'default' | 'lg', stringdefaultfalse, 'default'SelectTrigger size'sm' | 'default' | 'lg'default'default'SelectTrigger aria-invalidbooleandefaultfalseSelectValue placeholderReactNodeSelectContent size'default' | 'sm'default'default'SelectContent side, align, alignItemWithTriggerBase UIdefault'bottom', 'start', falseSelectGroup, SelectLabel, SelectSeparatorBase 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