SeatPicker
ImplementedCheckout-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).
components/organisms/SeatPicker/SeatPicker.tsxCheckout-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
SeatPicker
eventIdstring—Event id — echoed to `data-event` for analytics/event delegation.
sectionsSeatSection[][]Sections → rows → seats to render. Same data shape as SeatMap; empty renders the empty state.
selectedSeatsstring[][]Controlled selected seat ids. Drives aria-pressed, the selection ring, and the count line.
maxSeatsnumber8 (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—Emits the next selected-id array on toggle. Sold/blocked seats and readOnly mode never fire it.
readOnlybooleanfalseDisables every seat toggle and suffixes seat aria-labels with the localized 'read only' phrase.
locale"fr" | "en"'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.
headingstring'Seat picker' (fr: 'Sélecteur de siège')Section heading + root aria-label. Provide a localized string; built-in default is English.
seatCountLabelstring'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.
ofLabelstring'of'Infix of the counter line — 'of' in English, 'sur' in French.
emptyLabelstring'No seat map available'Empty-state copy when sections has no seats. Provide a localized string.
classNamestring—Appended to the root section element.
idstring`seatpicker-${useId()}`Root element id (autogenerated from useId when omitted).
<SeatPicker />Accessibility
Type
1 ruleARIA
1 rulea11y
1 rulebehavior
1 ruleUsage
Import
import { SeatPicker } from "@epasslive/design-system"Example 1
<SeatPicker eventId={e.id} sections={sections} maxSeats={4} locale="fr" seatCountLabel="Billets sélectionnés" ofLabel="sur" selectedSeats={sel} onSeatSelect={setSel} />Example 2
<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