Component Architecture Concepts
This page explains the architecture behind Seva’s web components, so you can make informed decisions when integrating them into your site.
Web components and custom elements
Section titled “Web components and custom elements”Seva components are web components — browser-native custom HTML elements that work in any framework or plain HTML. They’re built with Lit, a lightweight library for building web components.
Each component:
- Registers a custom element tag (e.g.,
<seva-auth>) - Uses Shadow DOM for style encapsulation — component styles don’t leak into your page, and your page styles don’t break the components
- Accepts configuration via HTML attributes (e.g.,
tenant-slug,api-url) - Communicates outward via custom DOM events (e.g.,
seva:authenticated)
Script loading
Section titled “Script loading”Each component is shipped as a standalone ES module:
seva-auth.jsseva-event-register.jsseva-cart.jsseva-my-profile.jsseva-my-registrations.jsseva-member-directory.jsLoad them with <script type="module">. Module scripts are deferred by default, meaning they don’t block page rendering. Shared code (Lit runtime, API client, etc.) is automatically split into a shared chunk (seva-shared-[hash].js) by the build system.
Attribute-driven configuration
Section titled “Attribute-driven configuration”Components are configured through HTML attributes:
<seva-event-register tenant-slug="my-club" event-slug="annual-gala" api-url="https://api.seva.tools"></seva-event-register>When an attribute changes, the component reacts automatically. For example, changing event-slug triggers a new API fetch for the updated event.
Some properties (like theme) are set via JavaScript rather than HTML attributes:
document.querySelector("seva-auth").theme = "dark";Event-driven communication
Section titled “Event-driven communication”Components communicate through standard DOM CustomEvents with bubbles: true and composed: true (meaning they cross Shadow DOM boundaries). All Seva events use the seva: prefix.
The communication pattern is unidirectional:
<seva-auth>firesseva:authenticated→ your glue script setsauth-tokenon other components<seva-event-register>firesseva:add-to-cart→<seva-cart>picks it up (automatically, via document-level listener)<seva-cart>firesseva:checkout-complete→<seva-event-register>refreshes to show the registered state
This event-based architecture means components are loosely coupled. You can use <seva-auth> without <seva-cart>, or use <seva-cart> with your own custom registration form, as long as you dispatch the correct events.
Modal components (<seva-my-profile>, <seva-my-registrations>, <seva-member-directory>) also consume seva:authenticated and seva:signed-out events from document. Additionally, <seva-my-profile> and <seva-my-registrations> can be opened by dispatching seva:open-my-profile or seva:open-my-registrations events on document.
Inline vs. modal components
Section titled “Inline vs. modal components”Seva components fall into two categories based on how they handle authentication and rendering:
Inline components
Section titled “Inline components”<seva-auth>, <seva-event-register>, and <seva-cart> render directly in the page flow. When used together, they require a glue script to propagate auth tokens between them (see Auth propagation).
However, <seva-event-register> embeds its own sign-in form internally — if event registration is the only authenticated feature on your page, you do not need a standalone <seva-auth>. The register component handles authentication on its own and shares the session with <seva-cart> via events. See <seva-auth>: Do you need this component? for guidance.
Modal components
Section titled “Modal components”<seva-my-profile>, <seva-my-registrations>, and <seva-member-directory> render a trigger button plus a modal overlay. They are architecturally distinct:
- Self-manage auth — They restore the session token from
localStorageon load and listen forseva:authenticated/seva:signed-outevents ondocument. No glue script is needed. - Embed sign-in — When opened without an active session, they show an embedded
<seva-auth>form inside the modal so the user can sign in without leaving the overlay. - Independent — They work independently of each other and of the inline components. You can use any combination on a page.
┌─────────────┐ │ seva-auth │ └──────┬──────┘ │ seva:authenticated │ ┌────────────┴────────────┐ │ │ ┌─────────▼─────────┐ ┌─────────▼──────────┐ │ INLINE COMPONENTS │ │ MODAL COMPONENTS │ │ (need glue script)│ │ (self-manage auth) │ │ │ │ │ │ event-register │ │ my-profile │ │ cart │ │ my-registrations │ │ │ │ member-directory │ └────────────────────┘ └──────────────────────┘
Your script sets Listen on document for auth-token attribute seva:authenticated + on each component restore from localStorageAuth token flow
Section titled “Auth token flow”Authentication tokens flow through HTML attributes, not a shared global store:
User signs in → seva-auth stores token in localStorage → seva-auth fires seva:authenticated → Your script reads auth.getToken() → Your script sets auth-token="..." on other components → Components use the token for API callsThis explicit wiring means you control exactly which components receive the token and when. It also means the auth component can be used on a different page from the other components — the token is persisted in localStorage and restored on page load.
localStorage persistence
Section titled “localStorage persistence”Two things are persisted in localStorage:
| Key pattern | Data | Component |
|---|---|---|
seva-auth-{tenantSlug} | User object and session token | <seva-auth>, <seva-event-register>, <seva-cart>, <seva-my-profile>, <seva-my-registrations>, <seva-member-directory> |
seva-cart-{tenantSlug} | Cart items (attendees, tickets, prices) | <seva-cart> |
Both are scoped to the tenant-slug value. Multiple tenants on the same domain won’t collide.
Shadow DOM and styling
Section titled “Shadow DOM and styling”Each component renders inside a Shadow DOM root. This means:
- Your CSS won’t affect component internals. You can’t style the sign-in form’s button by targeting
buttonin your stylesheet. - Component CSS won’t affect your page. The component’s styles are fully encapsulated.
- Layout works normally. Components participate in your page’s normal flow — you control their width, margin, and position from outside.
The theme property ('light' or 'dark') controls the component’s internal color scheme. Components use CSS custom properties internally for theming.
API client
Section titled “API client”Each component creates its own ApiClient instance (configured by tenant-slug and api-url). The API client:
- Adds the tenant slug to all requests
- Attaches the auth token (if available) as a Bearer token
- Handles error responses and surfaces them as component errors
Stripe integration
Section titled “Stripe integration”The <seva-cart> component dynamically imports @stripe/stripe-js only when a paid checkout is needed. Stripe is never loaded on pages where all registrations are free. The Stripe publishable key comes from the API during checkout, so you don’t need to configure it on the frontend.