Select
Lets people choose one option from a short list that opens from a button.
Preview
Installation
pnpm add @syntara/react @syntara/tokensimport { Select, SelectItem, SelectSection } from '@syntara/react';Usage
import { Select, SelectItem } from '@syntara/react';
export function PlanPicker() {
return (
<Select label="Plan" placeholder="Choose a plan" onSelectionChange={(key) => console.log(key)}>
<SelectItem id="starter">Starter</SelectItem>
<SelectItem id="team">Team</SelectItem>
<SelectItem id="business">Business</SelectItem>
</Select>
);
}Examples
Sections
Group long lists under headings with SelectSection.
Icons and descriptions
Items can show a leading icon and a second line; the trigger shows the icon and label only.
Controlled, from data
Render items from an array and control the selected key.
Selected key: in-review
Invalid and disabled
Required with an error message, and a disabled select.
Accessibility
| Keys | Action |
|---|---|
| EnterorSpaceor↓or↑ | Opens the list with the selected (or first/last) option focused. |
| ↓or↑ | Moves focus between options (skips disabled ones). |
| HomeorEnd | Moves focus to the first or last option. |
| EnterorSpace | Selects the focused option and closes the list. |
| Type characters | Jumps to the next option that starts with the typed text, open or closed. |
| Escape | Closes the list without changing the selection. |
| Tab | Closes the list and moves focus on. |
- The trigger is a button with aria-haspopup="listbox"; the label, description and error are linked to it.
- Options are role="option" with aria-selected; an item's description is linked with aria-describedby.
- Selected state is shown with a check mark and weight, not colour alone.
- A hidden native <select> keeps browser autofill and form submission working.
- Motion follows prefers-reduced-motion: the list still fades, but the spring entrance, the rows stepping in (first 8, 30ms apart), the press dip, the trigger's growing halo and the chevron flip only run when motion is allowed.
Guidelines
Do
- Use for 5–15 options that fit on screen; order them logically (alphabetical, frequency, or size).
- Keep option labels short; put detail in description.
- Use a placeholder that says what to pick ("Choose a plan"), not "Select…".
Don’t
- Don't use for 2–4 options people should compare at a glance — use a RadioGroup.
- Don't use for long lists people search by name — use a Combobox.
- Don't put actions in a Select — use a Menu.
API reference
Select
labelReactNodeVisible label. Use aria-label only when a visible label is impossible.
descriptionReactNodeHelp text under the trigger, linked with aria-describedby.
errorMessagestring | ((validation: ValidationResult) => string)Shown under the trigger when the field is invalid.
placeholderstringText in the trigger while nothing is selected.
Default
'Select an item' (localised)itemsIterable<T>Items for a dynamic list. Pair with a render function as children.
childrenRequiredReactNode | ((item: T) => ReactElement)SelectItem and SelectSection elements, or a function that renders one per item.
selectedKeyKey | nullThe selected item's id (controlled).
defaultSelectedKeyKeyThe initially selected item's id (uncontrolled).
onSelectionChange(key: Key | null) => voidCalled when the selection changes.
disabledKeysIterable<Key>Ids of items that can't be selected.
isOpenbooleanWhether the list is open (controlled). Pair with onOpenChange.
isRequiredbooleanMarks the field required and shows the label's required marker.
Default
falseisInvalidbooleanShows the invalid state and errorMessage.
Default
falseisDisabledbooleanDisables the trigger.
Default
falsenamestringForm field name; the selected key is submitted with forms.
sizesmmddefaultlg
Height of the box: md is the density's control height, sm one 8px step shorter, lg two steps taller. Height, corner radius and inline inset match a Button of the same size, so a field and a button in one row line up.
SelectItem
idRequiredKeyUnique key used by selectedKey and onSelectionChange.
childrenRequiredReactNodeThe option label. A string also becomes its textValue for type-ahead.
textValuestringPlain-text label for type-ahead and the trigger when children is not a string.
descriptionReactNodeA second, quieter line under the label. Not shown in the trigger.
iconReactNodeDecorative leading icon, shown in the list and the trigger.
isDisabledbooleanMakes this option unselectable.
Default
false
SelectSection
titleReactNodeHeading for the group; also its accessible name.
itemsIterable<T>Items for a dynamic section. Pair with a render function as children.
Tokens
The semantic tokens this component reads, grouped by what they control. Swatches show this site’s theme; change a tenant’s brand and the component follows with no code change.
- Colour13
border.strongsurface.defaulttext.defaulttext.subtlefocus.ringfeedback.danger.solidborder.defaultsurface.sunkentext.disabledsurface.raisedborder.subtlesurface.selectedsurface.canvas- Type10
font.size.mdline-height.normalfont.bodyfont.size.xsfont.weight.mediumline-height.snugfont.size.smfont.tracking.mdfont.tracking.smfont.tracking.xs- Space and size6
space.2control-heightcontrol-padding-inlinespace.1space.16space.4- Shape3
radius.fieldradius.containerradius.badge- Depth4
glass.opacityglass.blurshadow.overlayhairline- Motion6
motion.duration.fastmotion.easingmotion.duration.normalmotion.easing-outmotion.duration.springmotion.spring- Other2
sheenrim