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:authenticated │ ┌────────────┴────────────┐ │ │ ┌─────────▼─────────┐ ┌─────────▼──────────────────────┐ │ INLINE COMPONENTS │ │ MODAL COMPONENTS │ │ (need glue script)│ │ (self-manage auth) │ │ │ │ │ │ event-register │ │ event-register-modal │ │ event-register-cta│ │ my-profile │ │ cart │ │ my-registrations │ │ │ │ member-directory │ └────────┬───────────┘ └──────────────────────────────────┘ │ ▲ │ seva:add-to-cart │ seva:open-registration │ │ (fired by event-register-cta │ │ or your own button) ▼ │ ┌────────────────────┐ │ │ seva-cart │ │ │ → seva:cart-updated│ │ │ → seva:checkout- │ │ │ complete │ │ └────────────────────┘ │ │ event-register-cta ────────────────┘
Inline: your script Modal: listen on document sets auth-token attr for seva:authenticated + on each component restore from localStorageAuth 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>Note: Modal components (
<seva-my-profile>,<seva-my-registrations>,<seva-member-directory>) do not needpropagateAuth(). They automatically restore the token fromlocalStorageand listen forseva:authenticatedondocument. See Inline vs. modal components.
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 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; document.querySelector("seva-my-profile").theme = theme; document.querySelector("seva-my-registrations").theme = theme; document.querySelector("seva-member-directory").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, seva-event-register-cta | { eventSlug: string } |
| seva:registration-canceled | seva-event-register-cta | { eventSlug: string } |
| seva:registration-completed | seva-cart (post-checkout) | { eventSlug: string } |
| seva:open-registration | seva-event-register-cta (or your page script) | { eventSlug?: string, view?: EventRegisterView } |
| seva:cart-updated | seva-cart | { itemCount: number, totalCents: number } |
| seva:checkout-complete | seva-cart | { registrations: RegistrationResult[] } |
| seva:checkout-error | seva-cart | { message: string } |
| seva:open-my-profile | (your page script) | {} | Opens the profile modal. |
| seva:open-my-registrations | (your page script) | {} | Opens the registrations modal. |
| seva:profile-updated | seva-my-profile | {} | Profile or phone was saved. |
Note:
<seva-member-directory>does not have a document-level open event. Useelement.openModal()instead. See the<seva-member-directory>reference.