Skip to content

Seat Map

Choose seats from a venue plan, with price tiers, availability and keyboard navigation.

import { SeatMap, SeatMapRow, SeatMapSeat, SeatMapTier } from '@youj/react'

Installation & usage

npm i @youj/react

For an app already set up with npx @youj/cli init, which installs the peers (@youj/core, @youj/motion, motion and Base UI) and wires the provider. New app? Start at Installation.

Use a seat map when where someone sits is the choice: a concert, a cinema, a flight, a table plan. The stage sits at the top and the rows bend gently round it, split by aisles, with each row's letter at both ends. Seats take their price tier's color, taken seats are dimmed and hatched, and picked seats fill with their tier and spring in with a check. Pointing at or focusing a seat raises a chip over it with its row, number, tier and price, and the chip glides from seat to seat.

import { SeatMap, type SeatMapRow, type SeatMapTier } from '@youj/react'

const tiers: SeatMapTier[] = [
  { id: 'front', name: 'Front stalls', price: 68 },
  { id: 'rear', name: 'Rear', price: 32 },
]
const rows: SeatMapRow[] = ['A', 'B'].map((letter) => ({
  id: letter,
  label: letter,
  seats: Array.from({ length: 10 }, (_, i) => ({ id: `${letter}${i + 1}`, number: i + 1, tier: letter === 'A' ? 'front' : 'rear' })),
}))

<SeatMap rows={rows} tiers={tiers} aisles={[5]} value={picked} onValueChange={setPicked} max={6} />

rows run from the stage back, each with its seats in order along the row; a seat names its tier and can be unavailable. aisles lists the seat positions after which an aisle runs. value and onValueChange hold the picked seat ids (or use defaultValue), and max caps how many can be picked. Prices format in currency (EUR by default) and the provider's locale. stage changes the stage's word, or false leaves the stage out, for a table plan or a classroom. The legend under the map lists the tiers with their prices.

Seats shrink with the room down to about 17px. On a narrower screen the plan scrolls sideways instead, so seats stay big enough to tap. It opens on its center, where the stage is, and its edges fade by how far there is left to scroll. Each seat's hit area fills the gaps around it, and a seat you arrow to scrolls clear of the fades.

Reference

Propsrows, tiers, aisles… (12)

Props

SeatMap props
PropType
rowsRequiredSeatMapRow[]–
tiersRequiredSeatMapTier[]–
aislesnumber[]

Seat positions (1-based, counted along the row) after which an aisle runs.

default []

[]
valuestring[]

The picked seats' ids.

–
defaultValuestring[]–
onValueChange(value: string[]) => void–
maxnumber

How many seats can be picked. Default: no limit.

–
currencystring

ISO 4217 code for the prices.

default 'EUR'

'EUR'
format(price: number) => string

Formats a tier's price. Default: currency in the locale, without decimals.

–
localestring

Default: the YoujProvider locale.

–
labelstring

Accessible name. Default: the labels' "Seat map".

–
stagestring | false

The word on the stage, or false for no stage. Default: the labels' "Stage".

–
soundboolean

Plays toggle cues as seats are picked and let go.

default true

true
Other props pass through: extends ComponentProps<'div'>.
StylingParts and data attributes

Styling

Parts: seat-map, seat-map-stage, seat-map-grid, seat-map-row, seat-map-row-label, seat-map-seat (with data-state), seat-map-aisle, seat-map-inspector and seat-map-legend. The seat size is --seat, fitted to the width between 1.0625rem and 1.75rem.

MotionPick, let go, point or focus · reduced motion

Motion

Motion tokens
MomentTokens
Pickthe seat fills with its tier and the check springs in
spring.settle

0.45s, bounce 0.3

0.45s, bounce 0.3
Let gothe check shrinks away
duration.fastease.exit

140ms · cubic-bezier(0.4, 0, 1, 1)

140mscubic-bezier(0.4, 0, 1, 1)
Point or focusthe chip glides to the seat
duration.fastease.standard

140ms · cubic-bezier(0.2, 0, 0, 1)

140mscubic-bezier(0.2, 0, 0, 1)

Reduced motion

The check and the chip appear in place.

Reduced motion

A picked seat's check appears in place, and the chip moves to the next seat without gliding.

AccessibilityKeyboard, ARIA, announcements

Accessibility

  • The map is a grid named by label (default: the labels' "Seat map") and described by how to use it; each row is a row headed by its letter.
  • Each seat is a grid cell named by its row, number, price and status ("Row F, seat 12, €48, available"), with aria-selected when picked and aria-disabled when taken.
  • The grid is one tab stop. Arrows move seat to seat and row to row (skipping aisles), Home and End jump to a row's ends, Ctrl+Home and Ctrl+End to the first and last seat, and Space or Enter picks or lets go.
  • Picking and letting go are announced ("Row F, seat 12 added"), with the toggle cues; at the limit the pick is refused with the error cue and "You can pick up to 6 seats".
  • A venue plan is a physical map, so it isn't mirrored in RTL: seat 1 stays where the venue put it, and the arrow keys keep their direction. The text around it follows the page.
  • Its words come from the labels (seatMap, seatMapHint, seatMapStage, seatItem, seatAvailable, seatUnavailable, seatSelected, seatRow, seatNumber, seatAdded, seatRemoved, seatLimit), translated by the language packs.