Skip to content

How to build a seating chart in JavaScript and make it safe to sell from

To build a seating chart in JavaScript, render each seat as a button with a stable ID and an aria-pressed state, and keep the buyer’s selection in a Set.

This guide covers both halves: the prototype, then what it takes to sell from it. The same steps apply whether you call the result a seating chart or an interactive seat map.

01 · The prototype

Can you build a seating chart with plain JavaScript?

Yes, for a prototype. Every available seat is a button with a stable ID and an aria-pressed state, a Set holds the current selection, and sold seats stay disabled. Select a few seats below, then read exactly what that demo does and does not prove.

Studio Theatre · Row A–DBrowser only

No seats selected. This browser-only demo creates no hold.

selected = Set(0) {}

What this proves

  • Stable seat IDs
  • Available and unavailable states
  • Keyboard-operable selection
  • A readable selected-seat summary

What it does not prove

  • No atomic hold
  • No other buyer
  • No expiry or synchronization
  • No trusted price or booking
seating-chart.js · plain JavaScript
// <button data-seat="A-1" aria-pressed="false">A1</button>// <p id="summary" aria-live="polite"></p>const summary = document.querySelector('#summary');const selected = new Set();document.querySelectorAll('[data-seat]').forEach((seat) => {  seat.addEventListener('click', () => {    const id = seat.dataset.seat;    const active = !selected.has(id);    active ? selected.add(id) : selected.delete(id);    seat.setAttribute('aria-pressed', String(active));    renderSummary([...selected]);  });});function renderSummary(ids) {  summary.textContent = ids.length    ? `Selected locally: ${ids.join(', ')}`    : 'No seats selected.';}

That selected set is buyer intent, not inventory. Nothing here reserves a seat, so never label a locally selected seat as reserved. Everything above it is progressive enhancement: the markup ships as plain buttons, JavaScript upgrades them, and a reader without JavaScript still sees the seat labels, the sold states and the rest of this guide.

02 · React

How do you build a seating chart in React?

Keep the same contract: one button per seat, a stable id, aria-pressed for the selected state, and the selection in a Set. Hold that Set in useState and replace it on every toggle so React sees a new reference. Mutating the existing Set will not re-render.

SeatingChart.jsx · React
import { useState } from 'react';export function SeatingChart({ seats }) {  const [selected, setSelected] = useState(new Set());  const toggle = (id) => setSelected((prev) => {    const next = new Set(prev);    next.has(id) ? next.delete(id) : next.add(id);    return next;  });  return seats.map((seat) => (    <button key={seat.id} type="button" disabled={seat.sold}      aria-pressed={selected.has(seat.id)}      onClick={() => toggle(seat.id)}>{seat.label}</button>  ));}

This component is the React equivalent of the vanilla snippet: it renders seats and tracks intent, nothing more. It still has no inventory, no hold and no expiry, so two browsers can each “select” seat C-3.

Using Vue or Angular? Keep the same contract: hold the Set in a ref in Vue or a signal in Angular, and replace it on every toggle.

03 · Rendering

DOM, SVG, Canvas or WebGL for a seat map?

A renderer solves pixels, hit testing and navigation. It never decides who owns a seat when buyers compete. Choose by interaction cost and by how much accessibility work you are willing to rebuild. Whatever you pick, the accessible name, the focus order and the selected state have to survive it.

Pick a layer to see what lands in the page and what you still have to build.

LayerUseful whenWork you still own
Small maps, direct semantics, simple layoutsSpatial navigation, dense-scene performance, venue geometry
Vector geometry, labels, inspectable seat nodesPan/zoom, large node counts, semantic focus model
Dense 2D scenes and custom hit testingAccessible equivalent, focus, text, selection semantics
Very dense or spatially rich scenesRendering pipeline, picking, fallbacks, accessibility, device variance
Nodes in the page
21 buttons, one per seat
What a screen reader gets for free
Names, focus and the pressed state, from native buttons
Work you still own
Spatial navigation, dense-scene performance, venue geometry

Primary implementation references reviewed 10 Aug 2026: Konva’s React canvas reservation example, an open React/Konva seating editor, and the package documentation surfaced for react-seat-charts, seat-picker, and @seatsio/seatsio-react. Package status and APIs can change; confirm them at evaluation time.

04 · The production checklist

What does a seating chart need before you can sell from it?

The browser can ask for a seat. Only a server can decide who gets it. Six systems sit around the renderer. Miss one and the failure is not cosmetic: a seat sold twice, a hold that never releases, or an order your server cannot prove it was allowed to take.

Tick what your build already has

A renderer alone covers none of the six.

05 · Concurrency

How do I stop two buyers taking the same seat?

Not in the browser. A client may optimistically highlight a seat, but only the server can decide who gets it: selection asks for a hold, exactly one buyer gets the seat, and every other open map is told. Design the browser for the conflict response and not only the happy path: take the lost seat out of the map, keep the rest of the selection intact, and let the buyer carry on choosing.

  1. BrowserSeat selectedselection only
  2. Seat map serviceHold requestedone buyer gets the seat
  3. Every buyerHeld or takenall open maps update
  4. Your serverBooked or releasedconfirmed with your secret key and a booking reference
Two buyers, one contested seat: C-14
Buyer ASuccess

Holds C-14. On to checkout.

Buyer BConflict

C-14 was just taken. C-13 is still yours.

Seat map service
  1. A → hold C-14
  2. B → hold C-14
  3. A ← success: C-14 held
  4. B ← conflict: C-14 taken
  5. → every open map: C-14 held

Each run picks the winner at random, the way arrival order would. Whoever loses keeps the rest of their selection and carries on choosing.

The diagram explains the contract, not a universal throughput claim. SeatLayer’s released evidence includes one scoped 30-way race for one contested seat: one hold succeeded and the other requests received conflicts.

06 · The decision

Should you build a seating chart or use a library?

Build the parts that differentiate your product. DIY is a fair choice when the scope stays narrow; evaluate a maintained seating layer when seating itself is becoming a programme. The honest test is whether the seat map is the thing your customers pay for, or the thing standing between them and what they pay for.

Keep building when…

  • The map is static or rarely changes
  • One user or an internal workflow
  • No concurrent sale and no temporary hold
  • Your team wants to own rendering and venue authoring
  • Custom interaction is the differentiator

Evaluate an SDK when…

  • Venue charts are reused across events
  • Multiple buyers share live inventory
  • You need atomic holds, expiry, booking and webhooks
  • Buyer, organizer and staff surfaces all matter
  • Framework, server, mobile and operational support are expected

If you evaluate one, the SeatLayer packages on npm are @seatlayer/js for plain JavaScript and TypeScript, plus @seatlayer/react, @seatlayer/vue and @seatlayer/angular.

07 · Where SeatLayer fits

How does SeatLayer fit a JavaScript seating chart?

For a public event, SeatPicker uses event and a publishable publicKey to load chart plus live inventory directly from SeatLayer; your backend is not part of that first chart load. The picker hands your server an opaque holdId only at checkout. Your server inspects fresh held items, applies pricing and payment, and confirms inventory with a stable booking reference. A private buyer flow supplies a token provider instead of publicKey. The React seating chart tutorial walks the same flow end to end.

  1. Chart loadSeatLayer → browser · event and publicKey; your backend is not involved
  2. CheckoutPicker → your server · an opaque holdId
  3. BookingYour server → SeatLayer · fresh held items, your pricing and payment, a stable booking reference

JavaScript, TypeScript, React, Vue 3 and Angular 17+. Use the complete picker or the headless chart. Server SDKs and REST/OpenAPI cover the trusted side.

Production can start at $0: each Platform/SDK organization receives 100 free confirmed sold-seat credits per month. Paid packs cost $0.10 down to $0.05 per sold seat, purchased credits never expire, and there is no standard subscription commitment.

Terminal
$ npm install @seatlayer/js
checkout.js · @seatlayer/js
import { SeatPicker } from '@seatlayer/js';const picker = new SeatPicker({  container: '#seat-picker',  event: '<YOUR_EVENT_KEY>',  publicKey: '<YOUR_PUBLIC_KEY>',  onCheckout: async (_, __, handoff) => {    await fetch('/api/checkout', {      method: 'POST',      body: JSON.stringify({ holdId: handoff.holdId }),    });  },});picker.render();

Follow the JavaScript seat map SDK installation guide, or inspect @seatlayer/js on npm.

Pricing

Pay only when a seat sells.

Going live costs nothing, and 100 sold seats are free every month. After that you buy credits when you need them, and they never expire.

SDK · the seat map in your product

Pay per sold seat.

$0
to go live
100
sold seats free every month
10¢ → 5¢
per sold seat after that
  • No subscription and no monthly minimum
  • Credits you buy never expire
  • Holds and unsold seats are free
  • The SeatLayer badge goes away after your first credit pack

Keep building

JavaScript seating chart questions

6 questions

Yes. Plain JavaScript can render semantic seat buttons, track a local selected set, and update a summary. That is enough for a prototype or a static internal tool. Selling seats also needs seat state on a server, holds that only one buyer can win, expiry, booking, live updates and recovery.

Render one button per seat with a stable id and an aria-pressed value, and keep the selected seat ids in a Set held by useState. Replace the Set on every toggle so React re-renders. That component owns interaction only; inventory, holds, expiry, pricing, booking, and reconciliation still belong on the server.

There is no universal winner. DOM and SVG expose useful semantics and are straightforward for smaller maps. Canvas and WebGL can reduce DOM pressure for denser scenes but require more work for hit testing, focus, labels, and accessible equivalents. Test with your real venue and interaction requirements.

A selected flag in the browser cannot prevent a double sale. Seat state has to live on a server, and a click should ask it for a hold. When two buyers ask for the same seat at once, one gets it, the other is told it is taken, and every open map shows the change.

Several rendering libraries and React components exist, and they are a reasonable starting point for the visual layer. Most stop at rendering and local selection, so you still own inventory, holds, expiry, real-time updates, and booking. Evaluate a maintained seating platform when you need those server responsibilities as well. SeatLayer is one maintained option: install @seatlayer/js from npm, or @seatlayer/react, @seatlayer/vue or @seatlayer/angular, and live inventory, holds, booking and signed webhooks come with it.

Seat controls need keyboard operation, visible focus, meaningful names, programmatic selected and unavailable states, a readable selection summary, and a non-canvas path to the same decision. Zoom, pan, color, and pointer gestures cannot be the only way to understand or choose a seat.

Didn't find your answer?

Ready to test the hold and booking boundary?

Create test credentials and run one event through render, hold, trusted inspection, booking and signed webhook verification. To inspect first, open the released buyer demo.