Skip to content

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.

┌─────────────┐
│ 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 localStorage

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 need propagateAuth(). They automatically restore the token from localStorage and listen for seva:authenticated on document. See Inline vs. modal components.

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>

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.

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>

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.
});

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.

To programmatically sign a user out (e.g., from your site’s own navigation):

document.getElementById("auth").signOut();
// Then propagate the cleared token
propagateAuth();

| 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. Use element.openModal() instead. See the <seva-member-directory> reference.