UI framework decision — Web Components (via Lit)
Decision:
@stellar-passkey/uiships 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.LitElementbase class with reactive@propertyand 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
| Framework | Reason rejected |
|---|---|
| React-only | Excludes Vue/Svelte/vanilla. Requires version-tracking the React peer dep. |
| Vue-only | Smaller 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 wrappers | Maintenance burden; bindings drift behind the canonical component. The right home for these wrappers is community packages, not the kit core. |
Component roadmap
| Component | Issue | Status |
|---|---|---|
<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).