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. That prototype is enough to learn from; to sell seats you then need server-owned inventory, atomic holds with expiry, real-time updates and idempotent booking. This guide covers both halves. The same steps apply whether you call the result a seating chart or an interactive seat map.

Scope: the working demo below is deliberately browser-local. It teaches interaction, not double-booking prevention.

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–D
BROWSER ONLY

// <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.

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. 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.

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>
  ));
}

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.

LayerUseful whenWork you still own
DOM buttonsSmall maps, direct semantics, simple layoutsSpatial navigation, dense-scene performance, venue geometry
SVGVector geometry, labels, inspectable seat nodesPan/zoom, large node counts, semantic focus model
CanvasDense 2D scenes and custom hit testingAccessible equivalent, focus, text, selection semantics
WebGLVery dense or spatially rich scenesRendering pipeline, picking, fallbacks, accessibility, device variance

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.

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

The browser can request a change. Only an authoritative backend can decide whether that transition succeeds. 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.

Venue document

Stable seat, row, section, floor, category, label, accessibility and geometry identifiers that outlive any single event.

Authoritative inventory

Free, held, booked, blocked and released state owned outside the browser.

Atomic holds with expiry

One decision point for competing requests, and temporary ownership that ends predictably without relying on an open tab.

Real-time updates

Each buyer begins with a snapshot and applies later inventory deltas.

Trusted commerce and idempotent booking

Your server reads fresh held items, applies pricing and payment, and confirms with a stable booking reference so a retry is safe.

Reconciliation and operations

Signed webhooks, history and reports, plus staff controls, blocked seats, accessibility filters and scoped authorization.

How do I stop two buyers taking the same seat?

Not in the browser. A client may optimistically highlight a seat, but the durable transition belongs to one server-side state owner: selection requests an atomic hold, exactly one request wins, and every other client 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.

BROWSERSelected locallyintent only
→
SERVERHold requestedatomic decision
→
EVENT STATEHeld or conflictone authoritative result
→
TRUSTED SERVERBooked or releasedidempotent finalization
BUYER AHold C-14SUCCESS
C-14one state owner
BUYER BHold C-14CONFLICT

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.

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

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.

SeatLayer architecture: chart and event feed the buyer SDK; the SDK sends a hold ID to the host server; the host handles commerce and confirms inventory booking through the server API; signed webhooks return the outcome.
npm install @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();

JavaScript seating chart questions

Can I build a seating chart with plain JavaScript?
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 requires authoritative server inventory, atomic holds, expiry, booking, synchronization, and recovery.
How do I build a seating chart in React?
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.
DOM, SVG, Canvas or WebGL: which should a seat map use?
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.
How do I stop two buyers booking the same seat?
A frontend selected flag cannot prevent a double sale. The server must own seat state and serialize competing transitions. Selection should request an atomic hold; only one competing request can succeed, and every client must receive the resulting state change.
Is there a JavaScript seating chart library I can use instead of building?
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.
What makes a seating chart accessible?
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.

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.