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