Default
Show the codeHide the code
Default, code
const roles = ['Owner', 'Admin', 'Member', 'Viewer'];
<Select defaultValue="Member" label="Role">
{roles.map((r) => <SelectItem key={r} id={r}>{r}</SelectItem>)}
</Select>Choose one option from a list of up to about 15. Never submits on change.
These are the real React components. Use them with a mouse, touch or keyboard, and switch the theme and density for every example at once.
Default, code
const roles = ['Owner', 'Admin', 'Member', 'Viewer'];
<Select defaultValue="Member" label="Role">
{roles.map((r) => <SelectItem key={r} id={r}>{r}</SelectItem>)}
</Select>Placeholder, code
const roles = ['Owner', 'Admin', 'Member', 'Viewer'];
<Select placeholder="Choose a role" hint="Admins can invite and remove people." label="Role">
{roles.map((r) => <SelectItem key={r} id={r}>{r}</SelectItem>)}
</Select>Invalid, code
const roles = ['Owner', 'Admin', 'Member', 'Viewer'];
<Select placeholder="Choose a role" isRequired isInvalid errorMessage="Choose a role for this person" label="Role">
{roles.map((r) => <SelectItem key={r} id={r}>{r}</SelectItem>)}
</Select>Disabled, code
const roles = ['Owner', 'Admin', 'Member', 'Viewer'];
<Select defaultValue="Owner" isDisabled label="Role">
{roles.map((r) => <SelectItem key={r} id={r}>{r}</SelectItem>)}
</Select>Open, code
const roles = ['Owner', 'Admin', 'Member', 'Viewer'];
<Select defaultValue="Member" defaultOpen label="Role">
{roles.map((r) => <SelectItem key={r} id={r}>{r}</SelectItem>)}
</Select>The 30 design tokens Select reads, with the value each has in the theme and density chosen above. Change a token and the component follows, in Figma and in code.
| Token | Value now | CSS variable |
|---|---|---|
| color.border.subtle | … | --sd-color-border-subtle |
| color.text.disabled | … | --sd-color-text-disabled |
| color.text.secondary | … | --sd-color-text-secondary |
| elevation.2 | … | --sd-elevation-2 |
| input.bg | … | --sd-input-bg |
| input.bg-disabled | … | --sd-input-bg-disabled |
| input.border | … | --sd-input-border |
| input.border-hover | … | --sd-input-border-hover |
| input.border-invalid | … | --sd-input-border-invalid |
| input.fg | … | --sd-input-fg |
| input.height | … | --sd-input-height |
| input.padding-x | … | --sd-input-padding-x |
| input.placeholder | … | --sd-input-placeholder |
| input.radius | … | --sd-input-radius |
| layer.dropdown | … | --sd-layer-dropdown |
| listbox.bg | … | --sd-listbox-bg |
| listbox.border | … | --sd-listbox-border |
| listbox.check | … | --sd-listbox-check |
| listbox.option-fg | … | --sd-listbox-option-fg |
| listbox.option-height | … | --sd-listbox-option-height |
| listbox.option-hover | … | --sd-listbox-option-hover |
| listbox.option-selected | … | --sd-listbox-option-selected |
| listbox.padding | … | --sd-listbox-padding |
| listbox.radius | … | --sd-listbox-radius |
| motion.duration.fast | … | --sd-motion-duration-fast |
| motion.easing.standard | … | --sd-motion-easing-standard |
| radius.sm | … | --sd-radius-sm |
| select.chevron | … | --sd-select-chevron |
| spacing.gap.sm | … | --sd-spacing-gap-sm |
| spacing.inset.md | … | --sd-spacing-inset-md |
The npm package isn’t published yet. When it is, this is the install line; until then, this page runs the same build of the package (from commit fa3abf8).
Install
npm i @superduperui/react @superduperui/tokensImport
import { Select, SelectItem } from "@superduperui/react"Select also takes the props of the React Aria component it’s built on.
| Prop | Type | What it does |
|---|---|---|
labelrequired | string | Visible label (required). |
hint | string | |
errorMessage | string | ((validation: ValidationResult) => string) | |
items | Iterable<T> | Options as data; pair with a render function child. |
childrenrequired | ReactNode | ((item: T) => ReactNode) | SelectItem elements, or a render function for `items`. |
hideLabel | boolean | Keep the label for assistive technology but hide it visually (e.g. a role select in a member row under a "Role" column label). |
Screen readers announce "Role, Member, button, collapsed" then "Viewer, 4 of 4".
WCAG 2.2 AAA criteria the component meets in Comfortable density: 1.4.6, 1.4.9, 2.1.3, 2.3.2, 2.4.13, 2.5.5, 2.5.6, 2.3.3.
Compact density meets AA (2.5.8, 24px targets), not AAA.
Shared patterns it ships with: 3.2.5 (no auto-submit).
| axe-core (WCAG A, AA, AAA and best practice) | 120 runs across every story, theme, density and width: 0 violations |
|---|---|
| Rendered text contrast | 225 text runs: 0 below 7:1, lowest 8.43:1 |
| Target size | 36 targets: 0 under 44px in Comfortable |
| Focus appearance (2.4.13) | 63 focus stops in 3 browser engines: all pass |
| Keyboard script | Chromium 9 of 9, Firefox 9 of 9, WebKit 9 of 9 |
Manual passes with VoiceOver and NVDA haven’t been done yet.
Each step is scripted and run in Chromium, Firefox and WebKit.
| Key | What happens |
|---|---|
| Tab | Tab reaches the trigger |
| Enter | Enter opens the list on the selected option |
| ArrowDown | Down moves to the next option |
| Home | Home moves to the first option |
| End | End moves to the last option |
| Enter | Enter selects, closes and returns focus to the trigger |
| Alt+ArrowDown | Alt+Down opens |
| Type-ahead moves to the first match | |
| Escape | Esc closes without changing the value and returns focus |
The Figma component is Select, with State (Default, Hover, Focus, Open, Disabled, Invalid); Label visibility (Visible, Hidden). The Figma library isn’t published yet.
Figma to code: State (Default, Hover, Focus, Open, Disabled = isDisabled, Invalid = isInvalid); Label visibility → hideLabel (Visible = false, Hidden = true); Value → defaultValue (and the SelectItem); nested Label → label; nested Hint → hint; nested FieldError → errorMessage.