Interactive seat selection grid for cinemas, theaters, and events. Row/column layout with an aisle gap, tier-based pricing (standard/premium/VIP), occupied seats, a live selection summary, and a legend. Includes screen indicator at the top.
An interactive cinema seat picker: a centered card with a movie header, a gradient screen indicator, a row-by-row seat matrix with an aisle gap and tier-based per-row pricing (standard/premium/VIP), toggleable available seats and disabled taken seats, an available/selected/taken legend, and a sticky footer that live-sums the current selection into a per-tier breakdown, a running total, and a "Book seats" button.
Pro blocks install through the shadcn CLI with your license key and ship their full source. Docs and live previews stay open to everyone, so you can read every block's details first.
Swap YOUR_EMAIL and YOUR_KEY for the email and license key on your account. Find your license key on your account page.
Radix flavor
npx shadcn add "https://ui.beste.co/r/booking18?email=YOUR_EMAIL&license_key=YOUR_KEY"Base UI flavor
npx shadcn add "https://ui.beste.co/r-base/booking18?email=YOUR_EMAIL&license_key=YOUR_KEY"This installs the block to components/beste/block/booking18.tsx, plus the badge and button shadcn/ui primitives it uses for the section eyebrow and the footer book button.
The installed file exports booking18Demo alongside the block: the exact props behind the preview above. Spread it to get a working seat picker in one line.
import { Booking18, booking18Demo } from "@/components/beste/block/booking18";
export default function SeatSelectionPage() {
return <Booking18 {...booking18Demo} />;
}Then replace the demo with your own props. Written out, a trimmed setup looks like this:
import { Booking18 } from "@/components/beste/block/booking18";
export default function SeatSelectionPage() {
return (
<Booking18
badge={{ label: "Now showing", variant: "secondary" }}
heading="Pick your <strong>seats</strong>"
movieTitle="Dune: Part Three"
showtime="Tonight · 20:30 · Hall 4"
currency="$"
rows={["A", "B", "C", "D"]}
columns={10}
aisleAfter={5}
takenSeats={["A-3", "B-7", "C-2"]}
tierConfig={[
{ value: "standard", label: "Standard", price: 12 },
{ value: "premium", label: "Premium", price: 18 },
{ value: "vip", label: "VIP", price: 26 },
]}
rowTiers={{ A: "standard", B: "standard", C: "premium", D: "vip" }}
labels={{ screenLabel: "Screen", totalLabel: "Total", bookButton: "Book seats" }}
/>
);
}| Prop | Type | Default | Description |
|---|---|---|---|
badge | { label: string; variant?: "default" | "secondary" | "outline" } | – | Section eyebrow badge above the heading |
heading | string | – | Section heading, supports inline <strong> |
description | string | – | Muted paragraph under the heading |
movieTitle | string | – | Title in the card's movie header row |
showtime | string | – | Muted showtime line under the title |
currency | string | "$" | Symbol prefixed to every price and total |
rows | string[] | [] | Row labels rendered top to bottom, one seat row each |
columns | number | 12 | Seats per row, numbered 1..columns |
aisleAfter | number | 6 | Column index after which an aisle gap is inserted |
takenSeats | string[] | [] | Seat ids ("A-3") rendered disabled/occupied |
tierConfig | SeatTierConfig[] | [] | Tier definitions driving prices and legend chips |
rowTiers | Record<string, SeatTier> | {} | Maps each row label to its pricing tier |
labels | Booking18Labels | {} | Static UI strings (screen, legend, footer, button) |
className | string | – | Extra classes for the outer <section> |
type SeatTier = "standard" | "premium" | "vip";
type SeatTierConfig = {
value: SeatTier;
label: string;
price: number;
};
type Booking18Labels = {
screenLabel?: string;
seatsLabel?: string;
availableLegend?: string;
selectedLegend?: string;
takenLegend?: string;
noSelection?: string;
totalLabel?: string;
bookButton?: string;
};useState<Set<string>> keyed by `${rowLabel}-${col}` ids; it is never lifted out, so there is no onChange/onSelect prop to observe the current picks from outside.toggleSeat returns early when the id is in takenSet, and the button is also disabled, so occupied seats can never be selected.rowTiers, then its price in tierConfig; a seat whose row has no rowTiers entry contributes nothing to the total and nothing to the per-tier breakdown.selected.size === 0; clicking it only calls console.log("Seat booking:", ...), so persisting or advancing the booking is left to the integrator.heading is rendered via dangerouslySetInnerHTML, and inline <strong> inside it is styled bold and in the primary color.col === aisleAfter + 1, so setting aisleAfter beyond columns simply produces no gap.overflow-x-auto container, so wide rows/columns combinations scroll horizontally rather than shrinking the seats; VIP-tier available seats also get a decorative primary ring.labels.seatsLabel field exists on the Booking18Labels type but is not consumed anywhere in the render, so setting it has no effect.booking1
Split-panel booking interface with interactive monthly calendar on the left and time slot grid on the right. Features today highlight, disabled dates, selected state, morning/afternoon grouping, and booking summary footer.
booking5
Staff/provider selection cards with avatar, name, specialty, star rating, review count, availability badge, experience years, and selected state. Grid layout with "Any Provider" option.
booking20
Vehicle rental flow with pickup and return location fields, date range picker, car class grid (compact/suv/luxury) with image and specs, insurance tier selector, optional add-ons, and live price breakdown.
booking4
Service selection grid with category filter tabs, service cards showing icon, name, description, duration, price, and selected state. Ideal for spa, salon, clinic, and fitness booking flows.
booking9
Event ticket selection with event banner, date/venue info, ticket tier cards (General/VIP/Backstage) with perks list, quantity selector, and order total. Concert and conference ticket flow.
booking6
Restaurant table reservation with guest count selector, date/time picker, seating preference (indoor/outdoor/bar), special requests textarea, and booking summary. Fine dining reservation flow.