# Build a seating chart in JavaScript—then make it safe for live sales.

Rendering selectable seats is a frontend task. Selling them is a distributed state problem: authoritative inventory, atomic holds, expiry, real-time updates, trusted pricing, idempotent booking, and recovery.

Scope: the working example below is intentionally browser-local. It teaches interaction—not double-booking prevention.

1. 01
2. 02
3. 03
4. 04
5. 05
6. 06

## Start with semantic seats and explicit local state.

Each available seat is a button with a stable ID and an aria-pressed state. A JavaScript Set tracks the current browser selection. Sold seats are disabled.

JavaScript has not started. The guide remains readable.

### Interaction and semantics.

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

### No inventory authority.

- No atomic hold
- No other buyer
- No expiry or synchronization
- No trusted price or booking

## The selected set is buyer intent—not inventory.

This pattern is appropriate for learning, a static plan, or a narrow internal tool. It deliberately never labels local selection as a reservation.

```
// HTML: <button data-seat="A-1" aria-pressed="false">A1</button>
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.';
}
```

## Choose the render layer by interaction cost—not fashion.

A renderer solves pixels, hit testing, and navigation. It does not decide who owns a seat when buyers compete.

| Layer | Useful when | Work you still own |
| --- | --- | --- |
| DOM buttons | Small maps, direct semantics, simple layouts | Spatial navigation, dense-scene performance, venue geometry |
| SVG | Vector geometry, labels, inspectable seat nodes | Pan/zoom, large node counts, semantic focus model |
| Canvas | Dense 2D scenes and custom hit testing | Accessible equivalent, focus, text, selection semantics |
| WebGL | Very dense or spatially rich scenes | Rendering 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.

## A sellable seat map is nine systems around the renderer.

The browser can request a change. Only the authoritative backend can decide whether that transition succeeds.

### Venue document

Stable seat, row, section, floor, category, label, accessibility, and geometry identifiers.

### Authoritative inventory

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

### Atomic holds

One decision point for competing requests; failure returns a conflict, not a second success.

### Expiry

Temporary ownership ends predictably and returns inventory without relying on an open tab.

### Real-time updates

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

### Trusted commerce

Your server reads fresh held items and applies pricing, payment, customer, and order rules.

### Idempotent booking

A stable booking reference makes retry safe when a response is lost or delayed.

### Reconciliation

Signed webhooks, history, and reports recover outcomes beyond the request lifecycle.

### Operations + access

Staff controls, blocked seats, accessibility filters, responsive behavior, and scoped authorization.

## Selection becomes trustworthy only after the server agrees.

A client may optimistically highlight a seat, but the durable transition belongs to one event state owner.

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.

## Build the parts that differentiate your product.

DIY is a valid choice when the scope is deliberately narrow. An SDK is useful when the seating layer itself is becoming a product programme.

### A focused local component may be enough.

- Static or rarely changing map
- One user or internal workflow
- No concurrent sale or temporary hold
- Your team wants to own rendering and venue authoring
- Custom interaction is the differentiator

### The seating layer needs its own maintained platform.

- Reusable venue charts across events
- Multiple buyers and real-time inventory
- Atomic holds, expiry, booking, and webhooks
- Buyer, organizer, and staff surfaces
- Framework, server, mobile, and operational support

## Install the picker. Keep your commerce boundary.

SeatPicker renders the complete buyer surface and hands your server an opaque holdId. Your server inspects fresh held items, applies pricing and payment, and confirms inventory with a stable booking reference.

```
npm install @seatlayer/js

import { SeatPicker } from '@seatlayer/js';

const picker = new SeatPicker({
  container: '#seat-picker',
  event: 'ev_9f3a',
  onCheckout: async (_, __, handoff) => {
    await fetch('/api/checkout', {
      method: 'POST',
      body: JSON.stringify({ holdId: handoff.holdId }),
    });
  },
});

picker.render();
```

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

## Separate the visual choice from the inventory promise.

### 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 static internal tool. Selling seats also requires authoritative server inventory, atomic holds, expiry, booking, synchronization, and recovery.

### Should a seating chart use DOM, SVG, Canvas, or WebGL?

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 prevent two buyers from selecting 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.

### Does a React seating chart still need a backend?

Yes, when seats are being sold or reserved by multiple buyers. React can render the interface and manage local intent, but trusted inventory, hold expiry, pricing, booking, idempotency, and reconciliation belong on the server.

### What makes a JavaScript 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.

### When should I use a seating chart SDK instead of building?

Build when the job is a small static map, an internal tool, or a deliberately narrow custom interaction and your team wants to own every layer. Evaluate an SDK when you need reusable venue authoring, multi-buyer live inventory, holds, expiry, booking, webhooks, operational tools, and framework support as one maintained seating layer.

## Prove the hold and booking boundary before you build more UI.

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

---

_Machine-readable site index: [/llms.txt](https://seatlayer.io/llms.txt) · full corpus: [/llms-full.txt](https://seatlayer.io/llms-full.txt)_
_Source page: [https://seatlayer.io/guides/javascript-seating-chart/](https://seatlayer.io/guides/javascript-seating-chart/) · Questions: [Contact SeatLayer](https://seatlayer.io/contact/) · hello@seatlayer.io_
