Select Atom

A listbox, not a native <select>: typeahead, roving focus, scroll containment, and form association come from Base UI, and the popup is a real surface that can be themed and can hold an indicator per item.

Usage

Payouts convert at the daily rate.

tsx
<Field label="Settlement currency" hint="Payouts convert at the daily rate.">  <Select    items={[{ value: 'idr', label: 'Indonesian rupiah' },]}    value={value}    onValueChange={setValue}  /></Field>

Inside a Field, the Select picks up the label, the description wiring, and the error state without being told — the same seam every other control uses.

Choosing between this, a RadioGroup and a AutoComplete

How many options there are decides the control, and the numbers are not a house preference — usability testing puts a dropdown's useful band between roughly five and ten options. Below five, opening a menu to read choices that would have fitted on screen costs a click for nothing. Above ten, the list stops being scannable and typing beats scrolling.

OptionsReach forWhy
2–4RadioGroup, variant="segmented"They all fit at once; a menu hides them behind a press
5–10SelectEnough to be worth collapsing, few enough to scan
More than ~10AutoCompleteTyping is faster than scrolling, and the tail of a long list is never read
Too many to shipAutoComplete with loadItemsThe list lives behind a query rather than in the bundle

Note what the research says to do at the top of that table: replace the control, not add a search box to a dropdown. That is why searching lives in its own component here rather than behind a flag on this one (ADR 0014).

Sources: Baymard, Drop-Down Usability; NN/g, Dropdowns: Design Guidelines; GOV.UK Design System, Select.

Props

PropTypeDefaultNotes
itemsArray<{ value, label, disabled? }>The options. Labels are ReactNode, so an item can carry an icon or a secondary line.
value / defaultValuestring | nullControlled or uncontrolled.
onValueChange(value: string) => voidReceives the value only — Base UI’s event details do not leak through.
placeholderstring'Select…'Shown while nothing is chosen.
size'sm' | 'md' | 'lg''md'Matches the control height of Input at the same size.
namestringEmits a hidden input, so it posts inside a plain form.
disabled / invalidbooleanInside a Field, invalid is inherited.

Do

Keep option labels short enough to read in the trigger — the trigger shows the chosen label, not the value.

Don't

Use a Select for two options; that is a segmented RadioGroup or a Switch, and both show their choices without a click.

Keyboard

KeyBehaviour
Space / EnterOpens, and selects the highlighted item
/ Moves the highlight
TypingJumps to the first matching label
EscCloses without changing the value