Passer au contenu principal

Select

Implemented

Native select with visible label, helper text, and error state. Focus indication painted by the wrapper (focus-within ring); custom chevron. aria-describedby wired to helper/error. WCAG 2.5.5 tap-min enforced by wrapper height.

Categoryatoms
Sourcecomponents/atoms/Select/Select.tsx
AI HintUse for atomic UI elements. Compose into molecules for complex interactions. For searchable/async selection use SearchSelect (molecule); for multi-select ARIA widgets use Listbox (molecule).

WAI-ARIA listbox combobox with single/multi-select, type-ahead, optional search, async loading, and full keyboard navigation.

Select

Form input atom — Family 2 (Tier 1)

A controlled listbox (role="combobox") that supports single and multi-select, optional local search, debounced async loading, type-ahead, and a chip-rendered multi-value display. The trigger is a button; the listbox is a portal-free absolutely-positioned `<ul>` with active-descendant navigation.

Used for tax calculation.

This component supports light and dark mode.

Usage

Select is a controlled listbox. Pass options with stable valuekeys and always wire onChange. The component owns focus management, aria-activedescendant, type-ahead, Escape-to-close, and outside-click dismissal — never reimplement those on top of a native <select>.

import { useState } from 'react'
import { Select } from '@epasslive/design-system'

const COUNTRIES = [
  { value: 'ca', label: 'Canada' },
  { value: 'us', label: 'United States' },
  { value: 'fr', label: 'France' },
]

const [country, setCountry] = useState<string>('')

<Select
  label="Country"
  value={country}
  onChange={setCountry}
  options={COUNTRIES}
  placeholder="Pick a country"
  required
  hint="Used for tax calculation."
/>

Multi-select

Set multiple and pass an array as value. The component renders chips with individual remove buttons plus a "Clear all" affordance. The listbox receives aria-multiselectable="true" and each option renders a checkbox glyph.

const [tags, setTags] = useState<string[]>([])

<Select
  label="Audience"
  multiple
  value={tags}
  onChange={setTags}
  options={TAG_OPTIONS}
  placeholder="Add tags"
/>

Async loading

Provide an asyncLoad(query) function. The component debounces 300 ms and flips to a spinner state while in-flight. Stale responses are dropped (a cancelled-flag in the effect closure) so a slow earlier query never overwrites a newer one.

<Select
  label="Venue"
  value={venueId}
  onChange={setVenueId}
  options={[]}
  asyncLoad={async (q) => fetchVenues(q)}
  searchable
/>

Dark mode

The trigger swaps to ep-surface-card and the listbox to ep-surface-elevated with dark slate borders. The active-option highlight uses the teal token at 10% opacity so it stays readable on both themes. The chevron rotates 180° on open in both palettes.

Props

Prop
Type
Default
Description
id
string
—

Field id; auto-generated with React.useId() when omitted.

label*
string
—

Visible label above the trigger.

value*
T | T[]
—

Controlled value. Pass a single value for single mode, an array for multi mode.

onChange*
(value: T | T[]) => void
—

Change handler. Receives the new value (or array of values in multi mode).

options*
SelectOption<T>[]
—

Available options. Each has value, label, and optional disabled flag.

placeholder
string
—

Placeholder text shown when no value is selected.

multiple
boolean
false

Enables multi-select (chip rendering, clear-all, checkbox glyphs).

searchable
boolean
false

Embeds a search input at the top of the listbox that filters options locally.

asyncLoad
(query: string) => Promise<SelectOption<T>[]>
—

Optional async loader; debounced 300 ms. Replaces local options when set.

error
string
—

When set, swaps the trigger border to error, wires aria-invalid, and shows the message under the trigger.

hint
string
—

Helper text rendered below the trigger when there is no error.

disabled
boolean
false

Disables the trigger and chips.

required
boolean
false

Marks the field required (asterisk + native aria-required).

size
'sm' | 'md'
md

Trigger height; sm = h-8, md = h-10.

ariaLabel
string
—

Override for the trigger aria-label when label is not visible.

Props

Preview

Select

Prop
Type
Required
Default
Description
label
string
Required
—

Visible field label.

value
string
Required
—

Controlled value.

onChange
(value: string) => void
Required
—

Change handler.

options
SelectOption[]
Required
—

Option list ({label, value, disabled?}).

placeholder
string
Optional
—

Hidden disabled first option.

helperText
string
Optional
—

Helper wired via aria-describedby.

error
string
Optional
—

Error text; shows error border + role=alert.

required
boolean
Optional
False

Required marker + aria-required.

Quick use:<Select />

Accessibility

ARIA rolecombobox (native select)

ARIA

1 rule

WCAG 3.3.1

error message has role=alert and id wired via aria-describedby

Type

1 rule

NASA Rule 5

placeholder option is disabled+hidden (not selectable)

Usage

Import

tsxSnippet
import { Select } from "@epasslive/design-system"

Example 1

tsxSnippet
<Select label={t('venue')} value={v} onChange={setV} options={opts} />

Anti-patterns

— Do not use for search-as-you-type — SearchSelect is the searchable variant

Design System v4.1.0 | ePassLive