Skip to content

Select

Lets people choose one option from a short list that opens from a button.

Preview

PlanYou can change plans at any time.

Installation

pnpm add @syntara/react @syntara/tokens
import { 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.

Time zone

Icons and descriptions

Items can show a leading icon and a second line; the trigger shows the icon and label only.

Visibility

Controlled, from data

Render items from an array and control the selected key.

Status

Selected key: in-review

Invalid and disabled

Required with an error message, and a disabled select.

Reason for refundChoose a reason to continue.
RegionSet by your organisation.

Accessibility

KeysAction
EnterorSpaceor↓or↑Opens the list with the selected (or first/last) option focused.
↓or↑Moves focus between options (skips disabled ones).
HomeorEndMoves focus to the first or last option.
EnterorSpaceSelects the focused option and closes the list.
Type charactersJumps to the next option that starts with the typed text, open or closed.
EscapeCloses the list without changing the selection.
TabCloses 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

label
ReactNode

Visible label. Use aria-label only when a visible label is impossible.

description
ReactNode

Help text under the trigger, linked with aria-describedby.

errorMessage
string | ((validation: ValidationResult) => string)

Shown under the trigger when the field is invalid.

placeholder
string

Text in the trigger while nothing is selected.

Default 'Select an item' (localised)

items
Iterable<T>

Items for a dynamic list. Pair with a render function as children.

childrenRequired
ReactNode | ((item: T) => ReactElement)

SelectItem and SelectSection elements, or a function that renders one per item.

selectedKey
Key | null

The selected item's id (controlled).

defaultSelectedKey
Key

The initially selected item's id (uncontrolled).

onSelectionChange
(key: Key | null) => void

Called when the selection changes.

disabledKeys
Iterable<Key>

Ids of items that can't be selected.

isOpen
boolean

Whether the list is open (controlled). Pair with onOpenChange.

isRequired
boolean

Marks the field required and shows the label's required marker.

Default false

isInvalid
boolean

Shows the invalid state and errorMessage.

Default false

isDisabled
boolean

Disables the trigger.

Default false

name
string

Form field name; the selected key is submitted with forms.

size
  • sm
  • mddefault
  • lg

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

idRequired
Key

Unique key used by selectedKey and onSelectionChange.

childrenRequired
ReactNode

The option label. A string also becomes its textValue for type-ahead.

textValue
string

Plain-text label for type-ahead and the trigger when children is not a string.

description
ReactNode

A second, quieter line under the label. Not shown in the trigger.

icon
ReactNode

Decorative leading icon, shown in the list and the trigger.

isDisabled
boolean

Makes this option unselectable.

Default false

SelectSection

title
ReactNode

Heading for the group; also its accessible name.

items
Iterable<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