Vite + React quickstart
A client-only React app: no server, no backend, no database. The publishable key is public, the cart lives in the Live session, and payment happens in the hosted checkout, so a static bundle is all you deploy.
Install
npm create vite@latest my-tickets -- --template react-ts
cd my-tickets
npm install @ticketlayer/live @ticketlayer/elements @ticketlayer/elements-react
VITE_LIVE_API_URL=https://live.staging.t9r.dev
VITE_CHECKOUT_BASE=https://hbo.staging.t9r.dev
VITE_TL_PUBLISHABLE_KEY=tlpk_...
@ticketlayer/live, @ticketlayer/elements and @ticketlayer/elements-react
publish once their release workflows run; today the install fails. Until
then, the section Without the npm packages below
is what works, and it is the path to give an AI builder.
Boot once
import React from 'react';
import ReactDOM from 'react-dom/client';
import { createLiveClient } from '@ticketlayer/live';
import { defineCustomElements } from '@ticketlayer/elements-react';
import App from './App';
defineCustomElements();
createLiveClient({
baseUrl: import.meta.env.VITE_LIVE_API_URL,
publishableKey: import.meta.env.VITE_TL_PUBLISHABLE_KEY,
checkout: { mode: 'modal', embedBaseUrl: import.meta.env.VITE_CHECKOUT_BASE },
}).then((tl) => {
tl.on('checkout:completed', (order: { id: string }) => {
window.location.href = `/thanks?orderId=${encodeURIComponent(order.id)}`;
});
});
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>,
);
Use the elements
import { useState } from 'react';
import {
TlEventList,
TlBuyTicketsButton,
TlCartBadge,
TlCartDrawer,
TlOrderConfirmation,
} from '@ticketlayer/elements-react';
export default function App() {
const [eventId, setEventId] = useState<string | null>(null);
const orderId = new URLSearchParams(window.location.search).get('orderId');
if (orderId) return <TlOrderConfirmation />; // reads ?orderId= itself
return (
<div style={{ maxWidth: 960, margin: '2rem auto', fontFamily: 'system-ui' }}>
<header style={{ display: 'flex', justifyContent: 'space-between' }}>
<h1>What's on</h1>
<TlCartBadge />
</header>
<TlEventList columns={3} onTlEventClick={(e: CustomEvent<{ eventId: string }>) => setEventId(e.detail.eventId)} />
{eventId && (
<section>
<h2>Get tickets</h2>
<TlBuyTicketsButton eventId={eventId} size="lg" />
</section>
)}
<TlCartDrawer />
</div>
);
}
Every wrapper renders the matching custom element and turns each tl* event
into an onTl* prop with the CustomEvent as its argument. Attributes are
camelCased props (eventId, occurrenceId, fullWidth).
Without the npm packages
Load the CDN build from index.html and write the tags directly. This is what
works today and is also the smallest thing an AI tool can generate correctly.
<script src="https://cdn.staging.t9r.dev/v1/ticketlayer.js"
data-publishable-key="tlpk_..."
data-api-url="https://live.staging.t9r.dev"
data-checkout-base="https://hbo.staging.t9r.dev"
data-elements-url="https://cdn.staging.t9r.dev/elements/v1/ticketlayer-elements/ticketlayer-elements.esm.js"></script>
import { useEffect, useRef } from 'react';
export default function App() {
const list = useRef<HTMLElement>(null);
useEffect(() => {
const el = list.current;
const onClick = (e: Event) => console.log((e as CustomEvent<{ eventId: string }>).detail.eventId);
el?.addEventListener('tlEventClick', onClick);
return () => el?.removeEventListener('tlEventClick', onClick);
}, []);
return (
<>
<tl-cart-badge></tl-cart-badge>
<tl-event-list ref={list} columns="3"></tl-event-list>
<tl-buy-tickets-button event-id="evt_..."></tl-buy-tickets-button>
<tl-cart-drawer></tl-cart-drawer>
</>
);
}
TypeScript needs to know the tags exist:
import type { DetailedHTMLProps, HTMLAttributes } from 'react';
type TlProps = DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement> & Record<string, unknown>;
declare module 'react' {
namespace JSX {
interface IntrinsicElements {
'tl-event-list': TlProps;
'tl-buy-tickets-button': TlProps;
'tl-cart-badge': TlProps;
'tl-cart-drawer': TlProps;
'tl-order-confirmation': TlProps;
}
}
}
Attributes on raw tags are strings (columns="3"), and events are attached
with addEventListener because React does not bind custom events on
intrinsic elements. The checkout:completed hook is the same as in the HTML
quickstart: window.ticketlayer.ready.then((tl) => tl.on('checkout:completed', ...)).
Next
- Elements reference for every wrapper, prop and event
- Theming: set
--tl-*tokens in your CSS - Webhooks if you later add a server