Skip to content

LIVE DEMO · RUNS IN YOUR BROWSER

See how OpenSeat works, live.

Four steps, all running in your browser: generate a section with the SDK, design a venue, price it, and sell it with simulated live inventory. Nothing is sent anywhere.

  • The real @openseat packages
  • No sign-up, no backend
  • Mouse, keyboard or touch

ROW A · GENERATE

Generate a section with one function call.

Drag the sliders. Each change calls createBlockSection from @openseat/core and redraws the result on a real canvas. The createBlockSection call in the code panel runs with your values on every change.

What runs here

  • @openseat/corecreateBlockSectionvalidateLayout
  • @openseat/reactSeatMap
Generator snippets
8
16
20

Positive curvature bends rows around the stage in front. Seats keep their IDs when you change it.

8
30 px
Seat numbering
Direction
128seats8rowsvalidateLayout · valid
SeatMap · live
Generated section STAGE A B C D E F G H
Loading the live map…

Row A, as numbered

  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 15
  8. 16
generate.ts
import { createLayout, createBlockSection, validateLayout } from "@openseat/core"; const stalls = { id: "stalls", name: "Stalls", colour: "#2563EB" };const hall = createLayout({ id: "hall", name: "Main hall", width: 1200, height: 900, categories: [stalls] }); const { layout } = createBlockSection(hall, {  seating: {    kind: "block",    rows: 8,    seatsPerRow: 16,    curvature: 20,    seatSpacing: 30,    rowSpacing: 34,    align: "center",    aisles: [8],    aisleWidth: 30,    seatShape: { kind: "circle", radius: 11 },    seatLabeling: { format: "numeric", start: 1, step: 1, direction: "ltr" },  },  label: "Stalls",  categoryId: "stalls",  position: { x: 600, y: 420 },}); validateLayout(layout).valid; // true
The same call runs in your browser on every change. Developer snippet

ROW B · DESIGN

Design in the full visual editor.

This is LayoutEditor from @openseat/editor, the same designer organisers use. It starts on a 246-seat auditorium built from three parametric sections. Or send it your section from step 1.

What runs here

  • @openseat/editorLayoutEditoronChangeonSavecategoryPrices
Embed the designer
LayoutEditorCampus Auditorium (sample) · 246 seats
Stage

Best on a laptop.

Organisers design on a bigger screen. On a phone, skip to step 4: that's where your buyers will be.

Edits stay in this tab and carry into steps 3 and 4: the ticket types you add get prices, and the buyer map sells this layout.

OrganiserPage.tsx
import { useState } from "react";
import type { LayoutDocument } from "@openseat/core";
import { createRestAdapter } from "@openseat/adapter-rest";
import { LayoutEditor } from "@openseat/editor";
import "@openseat/editor/styles.css";

const api = createRestAdapter({
  baseUrl: "https://tickets.example.com/api",
  credentials: "include", // staff cookie session
  sendManifest: true, // PUT body: { layout, manifest }
});

export function OrganiserPage({ layout }: { layout: LayoutDocument }) {
  const [prices, setPrices] = useState<Record<string, string>>({ vip: "₹1,499", stalls: "₹499" });
  return (
    <div style={{ height: "100dvh" }}>
      <LayoutEditor
        initialLayout={layout}
        onSave={(request) => api.saveLayout(request)}
        categoryPrices={prices}
        onCategoryPricesChange={setPrices}
      />
    </div>
  );
}
The editor fills its container, so give it a height. api.saveLayout sends If-Match and turns a 409 into a conflict the editor shows.

How this embed is wired

initialLayout
Read once. This page remounts the editor when you load the sample or your step 1 section.
onChange
Fires after every edit. This page keeps the latest document, so steps 3 and 4 follow along.
onSave
Gets the layout, the revision it started from and an inventory manifest. Here an in-memory stand-in accepts every save.
categoryPrices
Prices are not part of the layout: the host owns them and the editor shows and edits them.

ROW C · PRICE

Price it: ticket types in ₹.

Ticket types live in the layout; prices live with your event. Change a price and the legend, the seat card and the totals update in the buyer map below.

What runs here

  • @openseat/reactcategoryPrices
  • @openseat/bookingbuildCart
categoryPrices API

Ticket types

From the layout in step 2. Prices are yours to set.

Currency
  • VIP

    42 seats · ₹62,958 if sold out

  • Stalls

    132 seats · ₹65,868 if sold out

  • Balcony

    72 seats · ₹21,528 if sold out

Full house

₹1,50,354

Display only: OpenSeat shows the prices you pass in and never converts currencies. Payment gateway fees, if any, are charged by the gateway.

What buyers see in the legend

  • VIP· ₹1,499
  • Stalls· ₹499
  • Balcony· ₹299
prices.tsx
// Ticket types live in the layout. Prices live with your event.const categoryPrices = {  vip: "₹1,499",  stalls: "₹499",  balcony: "₹299",}; // The buyer's map: legend, seat card and summary show these prices.<BookingMap layout={layout} statuses={statuses} categoryPrices={categoryPrices} /> // The designer shows and edits the same map of prices.<LayoutEditor  initialLayout={layout}  categoryPrices={categoryPrices}  onCategoryPricesChange={setCategoryPrices}/>

ROW D · SELL

Sell it: the buyer's booking map.

This is BookingMap from @openseat/react over an in-memory adapter that simulates other buyers. Pick up to six seats, press Hold and watch the countdown. Seats other buyers take turn amber, then grey, in real time.

What runs here

  • @openseat/reactBookingMapuseInventoryuseHold
  • @openseat/bookingcreateInMemoryBookingAdapterbuildCart
Booking map snippets

BookingMapSelling the Campus Auditorium sample

Campus Auditorium STAGE A B C D E F G H I J K L
Loading the live map…

Demo only: no payment is taken and the inventory lives in this tab. A real deployment holds seats on your server.

TicketPage.tsx
import { useState } from "react";
import type { LayoutDocument } from "@openseat/core";
import { createRestAdapter } from "@openseat/adapter-rest";
import { BookingMap, useHold, useInventory } from "@openseat/react";

const api = createRestAdapter({
  baseUrl: `${location.origin}/api`,
  credentials: "same-origin",
  realtime: { kind: "sse", url: (id) => `${location.origin}/api/v1/events/${encodeURIComponent(id)}/stream` },
});

export function TicketPage({ layout, eventId }: { layout: LayoutDocument; eventId: string }) {
  const { statuses } = useInventory(api, eventId); // live availability
  const hold = useHold(api, eventId, { maxTickets: 6, layout }); // server-timed countdown
  const [selection, setSelection] = useState<readonly string[]>([]);

  return (
    <BookingMap
      layout={layout}
      statuses={statuses}
      selectedSeatIds={selection}
      onSelectionChange={(next) => setSelection(next)}
      rules={{ maxSeats: 6, preventOrphans: true }}
      categoryPrices={{ vip: "₹1,499", stalls: "₹499", balcony: "₹299" }}
      renderActions={({ valid }) =>
        hold.state.status === "held" ? (
          // after your payment flow succeeds:
          <button onClick={() => hold.confirm("payment-ref-from-your-gateway")}>Confirm booking</button>
        ) : (
          <button disabled={!valid || !hold.canHold} onClick={() => hold.hold(selection)}>
            Hold seats
          </button>
        )
      }
    />
  );
}

From this demo to real sales

Swap the in-memory adapter for createRestAdapter. Nothing else on the page changes.

  1. 1

    useInventory

    Loads seat statuses from your API and keeps them live over polling or server-sent events.

  2. 2

    useHold

    Holds the chosen seats on your server, all or nothing, and counts down to the server's expiry.

  3. 3

    hold.confirm

    Call it after your payment flow succeeds. The seats become a booking.

  4. 4

    Your server

    Owns inventory, holds and payments. Or self-host the free OpenSeat server (public image coming soon): holds and bookings with database-enforced double-booking prevention.

CURTAIN CALL · RECAP

What you just used

Four packages ran above on one layout document, the same code you would ship. A fifth connects it to your API.

Put it in your own ticketing site.

OpenSeat is free and MIT licensed. Install the packages from npm (first release pending), embed the components you just used and connect them to your backend. Every export is in the API reference.

npm i @openseat/react @openseat/core @openseat/adapter-rest konva react react-dom · first npm release pending