Wiring Components Together
Seva components communicate through standard DOM custom events. No framework, state library, or message bus is needed — everything travels through the browser’s built-in event system.
The event flow
Section titled “The event flow”seva-auth seva-event-register seva-cart │ │ │ ├─ seva:authenticated ──────►│ │ │ (user, token) │ │ │ ├─ seva:add-to-cart ────────►│ │ │ (attendees, event) │ │ │ ├─ seva:cart-updated │ │ │ (itemCount, totalCents) │ │ ├─ seva:checkout-complete │ │ │ (registrations) ├─ seva:signed-out ─────────►│ │ │ │ │Auth propagation
Section titled “Auth propagation”The auth component does not automatically inject tokens into sibling components. Your page script must listen for auth events and set the auth-token attribute on components that need it.
<script> const auth = document.getElementById('auth'); const register = document.getElementById('register'); const cart = document.getElementById('cart');
function propagateAuth() { const token = auth.getToken(); if (token) { register.setAttribute('auth-token', token); cart.setAttribute('auth-token', token); } else { register.removeAttribute('auth-token'); cart.removeAttribute('auth-token'); } }
auth.addEventListener('seva:authenticated', propagateAuth); auth.addEventListener('seva:signed-out', propagateAuth);</script>Why attributes instead of a shared store?
Section titled “Why attributes instead of a shared store?”Web components use Shadow DOM for encapsulation. Attributes are the standard way to pass data into a custom element from the outside. This keeps each component self-contained and avoids hidden coupling.
Important: set attributes before modules load
Section titled “Important: set attributes before modules load”If you need to set attributes dynamically (e.g., reading tenant-slug from query parameters), use a non-module script placed before the module script tags. Module scripts are deferred by spec, so a regular script runs first:
<!-- This runs synchronously, before module scripts --><script> const params = new URLSearchParams(location.search); const tenant = params.get('tenant') || 'my-club';
document.getElementById('auth').setAttribute('tenant-slug', tenant); document.getElementById('register').setAttribute('tenant-slug', tenant); document.getElementById('cart').setAttribute('tenant-slug', tenant);</script>
<!-- These are deferred — they see the attributes above --><script type="module" src="YOUR_COMPONENTS_URL/seva-auth.js"></script><script type="module" src="YOUR_COMPONENTS_URL/seva-event-register.js"></script><script type="module" src="YOUR_COMPONENTS_URL/seva-cart.js"></script>Cart ← Registration (automatic)
Section titled “Cart ← Registration (automatic)”The <seva-cart> component listens on document for seva:add-to-cart events. Because <seva-event-register> fires this event with bubbles: true and composed: true, it reaches the document automatically. You don’t need any glue code for this connection.
Listening to cart updates
Section titled “Listening to cart updates”To display a cart badge or item count elsewhere on your page, listen for seva:cart-updated:
<span id="cart-badge">Cart: 0 items</span>
<script> document.addEventListener('seva:cart-updated', (e) => { const { itemCount, totalCents } = e.detail; const dollars = (totalCents / 100).toFixed(2); document.getElementById('cart-badge').textContent = `Cart: ${itemCount} item${itemCount !== 1 ? 's' : ''} ($${dollars})`; });</script>Reacting to checkout completion
Section titled “Reacting to checkout completion”After a successful checkout (free or paid), <seva-cart> fires seva:checkout-complete. The <seva-event-register> component automatically listens for this event and refreshes its view to show the registered state. If you need to trigger additional page behavior (e.g., redirect or show a thank-you message):
document.addEventListener('seva:checkout-complete', (e) => { console.log('Registrations confirmed:', e.detail.registrations); // Redirect, show a message, etc.});Theme synchronization
Section titled “Theme synchronization”All three components accept a theme property ('light' or 'dark'). To toggle them together:
function setTheme(dark) { const theme = dark ? 'dark' : 'light'; document.getElementById('auth').theme = theme; document.getElementById('register').theme = theme; document.getElementById('cart').theme = theme;}Note: theme is set as a JavaScript property, not an HTML attribute, because it is not reflected.
Sign-out from outside the component
Section titled “Sign-out from outside the component”To programmatically sign a user out (e.g., from your site’s own navigation):
document.getElementById('auth').signOut();// Then propagate the cleared tokenpropagateAuth();All seva:* events
Section titled “All seva:* events”| Event | Fired by | Payload |
|---|---|---|
seva:authenticated | seva-auth | { user: AuthUser, token: string } |
seva:signed-out | seva-auth | {} |
seva:error | seva-auth | { message: string } |
seva:view-changed | seva-auth, seva-event-register | { view: string } |
seva:event-loaded | seva-event-register | { event: EventDetail, tickets: TicketDetail[] } |
seva:add-to-cart | seva-event-register | { eventSlug, eventName, attendees, guestEmail?, hostMemberId? } |
seva:registration-error | seva-event-register | { message: string } |
seva:registration-declined | seva-event-register | { eventSlug: string } |
seva:cart-updated | seva-cart | { itemCount: number, totalCents: number } |
seva:checkout-complete | seva-cart | { registrations: RegistrationResult[] } |
seva:checkout-error | seva-cart | { message: string } |