Skip to main content

SeatPicker

Implemented

Checkout-oriented seat picker: sections → rows → seats grid reusing the SeatMap seat types and the same color + non-color status cues (WCAG 1.4.1 — dashed = held, dotted = blocked, strikethrough = sold/inaccessible). Adds a maxSeats cap (default 8, clamped 1–20) that disables unselected seats at the cap while deselection stays possible, a localized counter line, and a read-only confirmation mode. Pair with SeatMap (full chart) and SeatMapLegend (tier price legend).

Categoryorganisms
Sourcecomponents/organisms/SeatPicker/SeatPicker.tsx
AI HintUse in checkout/registration flows when the buyer picks a bounded number of specific seats. Pass sections (SeatMap shape), keep selection in state via selectedSeats + onSeatSelect, and set maxSeats to the order cap. For a full read-only venue chart use SeatMap; compose SeatMapLegend above when tiers need a price legend. locale switches en/fr aria templates and the counter separator; heading/seatCountLabel/ofLabel/emptyLabel are caller-localized strings.

Checkout-oriented seat picker with a maxSeats cap — sections → rows → seats with the five SeatMap statuses carrying color + non-color cues.

SeatPicker

design-system

SeatPicker preview and API reference.

Seat picker

Seats selected: 1 of 4

Orchestra

Balcony

Props

Preview

SeatPicker

Prop
Type
Required
Default
Description
eventId
string
Required
—

Event id — echoed to `data-event` for analytics/event delegation.

sections
SeatSection[]
Optional
[]

Sections → rows → seats to render. Same data shape as SeatMap; empty renders the empty state.

selectedSeats
string[]
Optional
[]

Controlled selected seat ids. Drives aria-pressed, the selection ring, and the count line.

maxSeats
number
Optional
8 (clamped 1–20)

Selection cap. At the cap unselected seats are disabled but selected seats stay toggleable (deselection is always allowed); non-finite values fall back to 8.

onSeatSelect
(seatIds: string[]) => void
Optional
—

Emits the next selected-id array on toggle. Sold/blocked seats and readOnly mode never fire it.

readOnly
boolean
Optional
false

Disables every seat toggle and suffixes seat aria-labels with the localized 'read only' phrase.

locale
"fr" | "en"
Optional
'en'

Localizes the built-in aria-label templates (Row/Rang, Seat/Siège), the five status words, and the counter separator (': ' en, no-break space + ':' fr). heading/seatCountLabel/ofLabel/emptyLabel stay caller-localized strings.

heading
string
Optional
'Seat picker' (fr: 'Sélecteur de siège')

Section heading + root aria-label. Provide a localized string; built-in default is English.

seatCountLabel
string
Optional
'Seats selected'

Label prefix of the counter line '{seatCountLabel}{sep}{n} {ofLabel} {cap}'. Word order is fixed — label first, infix before the cap (en/fr-safe); locales needing another order should hide the built-in line.

ofLabel
string
Optional
'of'

Infix of the counter line — 'of' in English, 'sur' in French.

emptyLabel
string
Optional
'No seat map available'

Empty-state copy when sections has no seats. Provide a localized string.

className
string
Optional
—

Appended to the root section element.

id
string
Optional
`seatpicker-${useId()}`

Root element id (autogenerated from useId when omitted).

Quick use:<SeatPicker />

Accessibility

ARIA roleapplication

Type

1 rule

NASA Rule 5

maxSeats clamped into 1–20 (default 8; non-finite falls back to 8)

ARIA

1 rule

WCAG 2.1.1, 4.1.2

Root section has role='application' aria-label={heading} aria-live='polite'; each row exposes 'Row/Rang {label}'; each seat button exposes '{Seat/Siège} {row}{number}, {status}' with aria-pressed and a localized read-only suffix

a11y

1 rule

WCAG 1.4.1

Seat status carries non-color cues (dashed=held, dotted=blocked, strikethrough=sold/inaccessible) mirroring SeatMap

behavior

1 rule

checkout order-cap contract

At the cap unselected seats disable while selected seats stay toggleable (deselection always allowed); sold/blocked seats never toggle

Usage

Import

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

Example 1

tsxSnippet
<SeatPicker eventId={e.id} sections={sections} maxSeats={4} locale="fr" seatCountLabel="Billets sélectionnés" ofLabel="sur" selectedSeats={sel} onSeatSelect={setSel} />

Example 2

tsxSnippet
<SeatPicker eventId={e.id} sections={sections} readOnly heading="Your seats" />

Anti-patterns

— Do not use for unbounded seat selection — the 1–20 cap is a hard contract

— Do not rebuild status styling locally — status cues mirror SeatMap; diverging breaks the family contract

— Do not compose SeatMap inside SeatPicker (it renders its own grid); the type-level reuse is intentional

Design System v4.1.0 | ePassLive