Skip to main content

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
.env
VITE_LIVE_API_URL=https://live.staging.t9r.dev
VITE_CHECKOUT_BASE=https://hbo.staging.t9r.dev
VITE_TL_PUBLISHABLE_KEY=tlpk_...
Not on npm yet

@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​

src/main.tsx
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​

src/App.tsx
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.

index.html
<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>
src/App.tsx
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:

src/tl-elements.d.ts
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​