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
Programme
free · MIT- 01GenerateA curved block of seats from one function call
createBlockSection@openseat/core - 02DesignThe full visual designer, embedded
<LayoutEditor />@openseat/editor - 03PriceTicket types with prices in ₹
categoryPrices@openseat/react - 04SellPick seats, hold them, confirm
<BookingMap /> · useHold@openseat/react + booking
ROW A · GENERATE
Generate a section with one function call.
Drag the sliders. Each change calls create 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/core
createBlock Section validateLayout - @openseat/react
SeatMap
Row A, as numbered
- 1
- 2
- 3
- 4
- 5
- 6
- 15
- 16
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; // trueROW B · DESIGN
Design in the full visual editor.
This is Layout 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.
Best on a laptop.
Organisers design on a bigger screen. On a phone, skip to step 4: that's where your buyers will be.
Loading the visual editor…
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.
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>
);
}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.
Ticket types
From the layout in step 2. Prices are yours to set.
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
// 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 Booking 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/react
BookingMap useInventory useHold - @openseat/booking
createIn Memory Booking Adapter buildCart
BookingMapSelling the Campus Auditorium sample
Demo only: no payment is taken and the inventory lives in this tab. A real deployment holds seats on your server.
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>
)
}
/>
);
}import { createInMemoryBookingAdapter } from "@openseat/booking";
import { useHold, useInventory } from "@openseat/react";
// For demos and tests only: the inventory lives in this tab.
const demo = createInMemoryBookingAdapter({
eventId: "demo",
layout,
holdMs: 10 * 60_000, // the 10:00 countdown
maxTicketsPerHold: 6,
});
// "Another buyer" holds two seats: every subscribed map updates at once.
// Use the layout's seat ids ("sec-stalls:r5:s4" is Stalls, row E, seat 4 in this sample).
demo.setStatus(["sec-stalls:r5:s4", "sec-stalls:r5:s5"], "held");
// In the component: the same hooks as with a real backend.
const { statuses } = useInventory(demo.adapter, "demo");
const hold = useHold(demo.adapter, "demo", { maxTickets: 6, layout });From this demo to real sales
Swap the in-memory adapter for create. Nothing else on the page changes.
- 1
useInventory
Loads seat statuses from your API and keeps them live over polling or server-sent events.
- 2
useHold
Holds the chosen seats on your server, all or nothing, and counts down to the server's expiry.
- 3
hold.confirm
Call it after your payment flow succeeds. The seats become a booking.
- 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.
- 01Generated a curved block@openseat/core
createBlock Section validateLayout - 02Designed a venue@openseat/editor
LayoutEditor onChange onSave - 03Set ticket prices@openseat/react · @openseat/editor
categoryPrices - 04Booked with a countdown@openseat/react + @openseat/booking
BookingMap useInventory useHold createIn Memory Booking Adapter - →In production: connect your API@openseat/adapter-rest
createRest Adapter
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