Skip to content

UI framework decision — Web Components (via Lit)

Decision: @stellar-passkey/ui ships framework-agnostic Web Components implemented with Lit. No React, Vue, or Svelte bindings live in this package.

Why Web Components

1. The upstream Stellar-Wallets-Kit is Web Components

Creit-Tech's @creit.tech/stellar-wallets-kit — the SCF-mandated integration target — ships its own modal and connect-button as Web Components, registered with the browser as custom elements (upstream README). Choosing the same primitive means a PasskeyModule consumer drops <passkey-create-button> into the exact same template position they already use for <stellar-wallets-button>, with the same DOM contract (slotted content, custom events). Familiar API surface, zero impedance.

2. Works everywhere

Web Components are part of the HTML5 standard and run unmodified in:

  • Vanilla HTML pages
  • React (post-19 with full ref / event support, see React docs on Web Components)
  • Vue 3 (vue.config.compilerOptions.isCustomElement)
  • Svelte (svelte:options customElement={true})
  • Solid, Angular, Qwik, Lit itself

A consumer importing @stellar-passkey/ui gets the same dropdown of custom elements regardless of which framework wraps the rest of their app.

3. Single bundle, no peer-dependency churn

A React component library has to track React majors (16 → 17 → 18 → 19), ship two builds for the new vs. legacy act() API, juggle peer-dependency ranges, and re-publish on each upstream release. A Web Component library has none of that overhead — the runtime is the browser, and the consumer's framework reaches in through standardised slots / attributes / events.

4. Lit is the right authoring layer

Authoring vanilla Web Components is verbose. Lit is a ~5 KB (minified + gzipped) library from Google that adds reactive properties, templating, and scoped CSS while compiling down to native Custom Elements. It is used by Stellar-Wallets-Kit's own components, Adobe Spectrum, and many other production component libraries.

Lit gives us:

  • @customElement("passkey-create-button") declarative registration.
  • LitElement base class with reactive @property and shadow-DOM encapsulation, so kit styles never leak into the host page.
  • A templating syntax that's TypeScript-friendly and tree-shakable.
  • First-class support for slots — consumers can override the inner layout while keeping the kit's interaction logic.

Trade-offs we accept

  • Shadow DOM styling is opt-in. Consumers who want to deeply restyle the kit must use ::part(…) selectors and CSS custom properties. We expose both. Documented per-component.
  • Server-side rendering (Next.js, Nuxt) requires a <script type="module"> boundary or @lit/ssr. We don't ship SSR adapters in this MVP — covered in the README.
  • React 18 and earlier had Web Component edge cases around custom events and properties. React 19 closes those gaps; we recommend ≥ 19, and the kit's API surface intentionally avoids the problematic patterns.

Alternatives considered

FrameworkReason rejected
React-onlyExcludes Vue/Svelte/vanilla. Requires version-tracking the React peer dep.
Vue-onlySmaller share of Stellar dapp ecosystem; same lock-in problem.
Headless logic-only (no UI)Forces every consumer to rebuild common UX (passkey prompt copy, error mapping). Increases the bug surface.
Multi-framework wrappersMaintenance burden; bindings drift behind the canonical component. The right home for these wrappers is community packages, not the kit core.

Component roadmap

ComponentIssueStatus
<passkey-create-button>YK-258 (PSK-027)Pending
<passkey-sign-tx>YK-259 (PSK-028)Pending
<passkey-recover>YK-260 (PSK-029)Pending

This package (@stellar-passkey/ui) is the home for all three plus any follow-ups (e.g. inline error-banner component for the YK-251 error taxonomy).

MIT — SCF-43 RFP submission (2026). Status: pre-1.0.