@zitadel/components 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +346 -0
  3. package/dist/atoms/index.d.mts +2 -0
  4. package/dist/atoms/index.d.ts +9 -0
  5. package/dist/atoms/index.d.ts.map +1 -0
  6. package/dist/atoms/index.mjs +2 -0
  7. package/dist/atoms/zl-alert.d.ts +56 -0
  8. package/dist/atoms/zl-alert.d.ts.map +1 -0
  9. package/dist/atoms/zl-button.d.ts +83 -0
  10. package/dist/atoms/zl-button.d.ts.map +1 -0
  11. package/dist/atoms/zl-card.d.ts +46 -0
  12. package/dist/atoms/zl-card.d.ts.map +1 -0
  13. package/dist/atoms/zl-field.d.ts +73 -0
  14. package/dist/atoms/zl-field.d.ts.map +1 -0
  15. package/dist/atoms/zl-icon.d.ts +48 -0
  16. package/dist/atoms/zl-icon.d.ts.map +1 -0
  17. package/dist/atoms/zl-page-shell.d.ts +42 -0
  18. package/dist/atoms/zl-page-shell.d.ts.map +1 -0
  19. package/dist/atoms/zl-passkey.d.ts +118 -0
  20. package/dist/atoms/zl-passkey.d.ts.map +1 -0
  21. package/dist/atoms/zl-pill.d.ts +55 -0
  22. package/dist/atoms/zl-pill.d.ts.map +1 -0
  23. package/dist/atoms-CmOTltTH.mjs +1311 -0
  24. package/dist/atoms-CmOTltTH.mjs.map +1 -0
  25. package/dist/index-7FDHEpv5.d.mts +996 -0
  26. package/dist/index-7FDHEpv5.d.mts.map +1 -0
  27. package/dist/index-DIUFZjXw.d.mts +6696 -0
  28. package/dist/index-DIUFZjXw.d.mts.map +1 -0
  29. package/dist/index-DaOSCm0T.d.mts +249 -0
  30. package/dist/index-DaOSCm0T.d.mts.map +1 -0
  31. package/dist/index-bZEvwHtq.d.mts +996 -0
  32. package/dist/index-bZEvwHtq.d.mts.map +1 -0
  33. package/dist/index.d.mts +31 -0
  34. package/dist/index.d.mts.map +1 -0
  35. package/dist/index.d.ts +16 -0
  36. package/dist/index.d.ts.map +1 -0
  37. package/dist/index.mjs +6 -0
  38. package/dist/internal/base64url.d.ts +18 -0
  39. package/dist/internal/base64url.d.ts.map +1 -0
  40. package/dist/internal/unique-id.d.ts +2 -0
  41. package/dist/internal/unique-id.d.ts.map +1 -0
  42. package/dist/manifest-DCC2FxCE.d.mts +39 -0
  43. package/dist/manifest-DCC2FxCE.d.mts.map +1 -0
  44. package/dist/manifest.d.ts +36 -0
  45. package/dist/manifest.d.ts.map +1 -0
  46. package/dist/manifests.d.mts +9 -0
  47. package/dist/manifests.d.mts.map +1 -0
  48. package/dist/manifests.d.ts +6 -0
  49. package/dist/manifests.d.ts.map +1 -0
  50. package/dist/manifests.mjs +30 -0
  51. package/dist/manifests.mjs.map +1 -0
  52. package/dist/orchestrator/api-client.d.ts +33 -0
  53. package/dist/orchestrator/api-client.d.ts.map +1 -0
  54. package/dist/orchestrator/branding-to-tokens.d.ts +28 -0
  55. package/dist/orchestrator/branding-to-tokens.d.ts.map +1 -0
  56. package/dist/orchestrator/branding-validator.d.ts +7 -0
  57. package/dist/orchestrator/branding-validator.d.ts.map +1 -0
  58. package/dist/orchestrator/branding.d.ts +76 -0
  59. package/dist/orchestrator/branding.d.ts.map +1 -0
  60. package/dist/orchestrator/font-loader.d.ts +2 -0
  61. package/dist/orchestrator/font-loader.d.ts.map +1 -0
  62. package/dist/orchestrator/index.d.mts +2 -0
  63. package/dist/orchestrator/index.d.ts +25 -0
  64. package/dist/orchestrator/index.d.ts.map +1 -0
  65. package/dist/orchestrator/index.mjs +2 -0
  66. package/dist/orchestrator/liquid.d.ts +28 -0
  67. package/dist/orchestrator/liquid.d.ts.map +1 -0
  68. package/dist/orchestrator/locales/de.d.ts +9 -0
  69. package/dist/orchestrator/locales/de.d.ts.map +1 -0
  70. package/dist/orchestrator/locales/en.d.ts +13 -0
  71. package/dist/orchestrator/locales/en.d.ts.map +1 -0
  72. package/dist/orchestrator/locales/index.d.ts +12 -0
  73. package/dist/orchestrator/locales/index.d.ts.map +1 -0
  74. package/dist/orchestrator/mandatory-gates.d.ts +26 -0
  75. package/dist/orchestrator/mandatory-gates.d.ts.map +1 -0
  76. package/dist/orchestrator/sanitiser.d.ts +6 -0
  77. package/dist/orchestrator/sanitiser.d.ts.map +1 -0
  78. package/dist/orchestrator/template-context.d.ts +55 -0
  79. package/dist/orchestrator/template-context.d.ts.map +1 -0
  80. package/dist/orchestrator/template-names.d.ts +16 -0
  81. package/dist/orchestrator/template-names.d.ts.map +1 -0
  82. package/dist/orchestrator/templates/auth-form.liquid.d.ts +14 -0
  83. package/dist/orchestrator/templates/auth-form.liquid.d.ts.map +1 -0
  84. package/dist/orchestrator/templates/default.liquid.d.ts +23 -0
  85. package/dist/orchestrator/templates/default.liquid.d.ts.map +1 -0
  86. package/dist/orchestrator/templates/passkey-upsell.liquid.d.ts +15 -0
  87. package/dist/orchestrator/templates/passkey-upsell.liquid.d.ts.map +1 -0
  88. package/dist/orchestrator/templates/signed-in.liquid.d.ts +20 -0
  89. package/dist/orchestrator/templates/signed-in.liquid.d.ts.map +1 -0
  90. package/dist/orchestrator/theme-controller.d.ts +30 -0
  91. package/dist/orchestrator/theme-controller.d.ts.map +1 -0
  92. package/dist/orchestrator/zitadel-login.d.ts +182 -0
  93. package/dist/orchestrator/zitadel-login.d.ts.map +1 -0
  94. package/dist/orchestrator/zitadel-logout.d.ts +86 -0
  95. package/dist/orchestrator/zitadel-logout.d.ts.map +1 -0
  96. package/dist/orchestrator-CNKo2WQG.mjs +3336 -0
  97. package/dist/orchestrator-CNKo2WQG.mjs.map +1 -0
  98. package/dist/styles/base.d.ts +7 -0
  99. package/dist/styles/base.d.ts.map +1 -0
  100. package/dist/styles/focus-ring.d.ts +7 -0
  101. package/dist/styles/focus-ring.d.ts.map +1 -0
  102. package/dist/styles/index.d.ts +5 -0
  103. package/dist/styles/index.d.ts.map +1 -0
  104. package/dist/styles/surface.d.ts +8 -0
  105. package/dist/styles/surface.d.ts.map +1 -0
  106. package/dist/styles/tokens.d.ts +11 -0
  107. package/dist/styles/tokens.d.ts.map +1 -0
  108. package/dist/tokens/index.d.mts +2 -0
  109. package/dist/tokens/index.d.ts +9 -0
  110. package/dist/tokens/index.d.ts.map +1 -0
  111. package/dist/tokens/index.mjs +2 -0
  112. package/dist/tokens-TXmurodq.mjs +244 -0
  113. package/dist/tokens-TXmurodq.mjs.map +1 -0
  114. package/dist/tsconfig.lib.tsbuildinfo +1 -0
  115. package/package.json +94 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ZITADEL
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,346 @@
1
+ # @zitadel/components
2
+
3
+ Lit-based atomic web components and the `<zitadel-login>` orchestrator for the
4
+ Zitadel auth UI.
5
+
6
+ The package exports:
7
+
8
+ - **Atoms** — `<zl-field>`, `<zl-button>`, `<zl-alert>`, `<zl-icon>`,
9
+ `<zl-pill>`, `<zl-card>`, `<zl-page-shell>`. Form-associated, accessible,
10
+ branding-aware Lit elements that map 1:1 to the flow API
11
+ field/action/error primitives and the Figma design system.
12
+ - **Orchestrator** — `<zitadel-login>`. A single drop-in element that calls
13
+ the flow API, renders each step through a Liquid template, manages focus
14
+ and form submission, and applies branding/theme/locale.
15
+ - **Tokens & manifests** — design tokens (`--zl-*` CSS custom properties), a
16
+ Liquid template registry, and per-atom manifests describing allowed
17
+ attributes/parts/events for sanitiser allowlists.
18
+
19
+ It is consumed directly by tenants embedding the auth UI on their own pages
20
+ and indirectly by the `apps/console` shell.
21
+
22
+ ## Status
23
+
24
+ Pre-release substrate. APIs are stabilising alongside
25
+ [`docs/design/branding/`](../../docs/design/branding/) and
26
+ [`docs/design/flowengine/`](../../docs/design/flowengine/). Expect minor breaks
27
+ until the first published version. See
28
+ [`docs/design/branding/form-participation.md`](../../docs/design/branding/form-participation.md)
29
+ for the form-association / accessibility decisions baked into every input atom.
30
+
31
+ ## Install
32
+
33
+ ```sh
34
+ corepack pnpm add @zitadel/components
35
+ ```
36
+
37
+ `lit`, `liquidjs`, and `dompurify` are peer/runtime deps and are intentionally
38
+ externalised so npm consumers dedupe with their own copies.
39
+
40
+ ## Quickstart — drop on a page
41
+
42
+ The 90% case: render the element, point the typed Flow API client at your
43
+ backend, set a locale.
44
+
45
+ ```html
46
+ <script type="module" src="@zitadel/components"></script>
47
+
48
+ <zitadel-login id="login" purpose="login" project-id="proj_123"></zitadel-login>
49
+
50
+ <script type="module">
51
+ import { setProxyPath } from '@zitadel/api/runtime/base-url';
52
+
53
+ setProxyPath('https://api.tenant.com');
54
+
55
+ const el = document.getElementById('login');
56
+ el.locale = await fetch('/i18n/en.json').then((r) => r.json());
57
+ </script>
58
+ ```
59
+
60
+ What `<zitadel-login>` handles for you:
61
+
62
+ - `POST /flow` on mount, `POST /flow/{id}/submit` for every step, terminal
63
+ `redirect` / `show` honoured automatically.
64
+ - Native `<form>` semantics: Enter submits, password managers see the inputs,
65
+ `<button type="submit">` works, browser autofill works.
66
+ - Focus moved to the first field on every step change.
67
+ - Branding tokens mapped to CSS variables on the shadow root.
68
+ - Light/dark theme via `prefers-color-scheme`, hot-swappable.
69
+ - Mandatory gates (terms, captcha) enforced before submit.
70
+ - Output sanitised with DOMPurify against a per-atom allowlist before injection.
71
+ - Stateless server: the `_zflow` HttpOnly cookie is the source of truth
72
+ between requests; every call runs with `credentials: "include"`.
73
+
74
+ ### React / Astro / Next
75
+
76
+ Same element, lifted into JSX. Pass objects through `ref` rather than as
77
+ attributes (web component properties are typed objects, not stringified
78
+ attributes).
79
+
80
+ ```tsx
81
+ import '@zitadel/components';
82
+ import { setProxyPath } from '@zitadel/api/runtime/base-url';
83
+
84
+ setProxyPath(import.meta.env.VITE_ZITADEL_API_BASE);
85
+
86
+ export function Login({ locale }: Props) {
87
+ return (
88
+ <zitadel-login
89
+ purpose="login"
90
+ project-id="proj_123"
91
+ ref={(el) => {
92
+ if (!el) return;
93
+ el.locale = locale;
94
+ }}
95
+ />
96
+ );
97
+ }
98
+ ```
99
+
100
+ ### Mocking for offline demos and tests
101
+
102
+ The orchestrator calls the typed `@zitadel/api` fetch client
103
+ directly — there is no transport abstraction to swap. Intercept at the
104
+ network layer via the workspace-internal `@zitadel/api-mock`
105
+ package, which walks an xstate flow machine through identifier →
106
+ password → done (with register and SSO branches) using orval-typed step
107
+ fixtures:
108
+
109
+ - **Tests (`msw/node`)** — pass `setupMockHandlers()` from
110
+ `@zitadel/api-mock` into `setupServer(...)`.
111
+ `getCapturedRequests()` exposes captured request bodies for assertions.
112
+ - **Dev playgrounds / browser (`msw/browser`)** — `setupMock(worker)`; see
113
+ [`dev/main.ts`](dev/main.ts) for a working setup. `applyBranding(...)`
114
+ injects a tenant branding overlay merged into every response (presets
115
+ include `font_url` for Inter).
116
+ - **Framework demos (TCP server)** — `corepack pnpm nx start @zitadel/api-mock`
117
+ serves the same handlers on port 4000 with `defaultDevBranding` (Arimo
118
+ `font_url`) applied at boot. See [`apps/demo-next`](../../apps/demo-next/README.md)
119
+ and [`apps/demo-nuxt`](../../apps/demo-nuxt/README.md).
120
+
121
+ ### Two preview surfaces
122
+
123
+ Two places run the components against `@zitadel/api-mock`. They
124
+ have different jobs — keep both:
125
+
126
+ | Surface | Nx command | URLs | What it gives you |
127
+ | --- | --- | --- | --- |
128
+ | **Lit playground** | `corepack pnpm nx dev @zitadel/components` | [login](http://localhost:5173/?route=login) · [atoms](http://localhost:5173/?route=atoms) | Component author surface: branding presets, event log, source TS from `src/`. MSW runs in the browser — no TCP mock server. |
129
+ | **React console playground** | `corepack pnpm nx dev @zitadel/console` | [http://localhost:5174](http://localhost:5174) | `@zitadel/ui-react` atom matrices in the pre-release console shell. MSW in `import.meta.env.DEV`. Compare against Lit `:5173/?route=atoms` in a second tab. |
130
+ | **demo-next** | `corepack pnpm nx start @zitadel/api-mock` + `ZITADEL_URL=http://localhost:4000 corepack pnpm nx dev @zitadel/demo-next` | [http://localhost:3002/login](http://localhost:3002/login) (mock on `:4000`) | Next.js SDK, middleware, cookies, built `dist/`. See [`apps/demo-next`](../../apps/demo-next/README.md). |
131
+ | **demo-nuxt** | mock on `:4000`, then `ZITADEL_URL=http://localhost:4000 corepack pnpm nx dev @zitadel/demo-nuxt` | [http://localhost:3001/login](http://localhost:3001/login) | Nuxt SDK, middleware, cookies, built `dist/`. See [`apps/demo-nuxt`](../../apps/demo-nuxt/README.md). |
132
+
133
+ The Lit dev playground iterates on `<zl-*>` source; the React console
134
+ playground exercises `@zitadel/ui-react` in the internal shell.
135
+ When tweaking Lit visuals or shadow-DOM behaviour, use `:5173` first —
136
+ it round-trips faster. For React pair tweaks, use `:5174`.
137
+
138
+ **Stale Lit styles on `:5173`?** Atom `.ts` changes use
139
+ `vite-plugin-web-components-hmr` (Lit HMR). Edits to `shared-component-styles`
140
+ CSS alone trigger a full reload. If it still looks old, run
141
+ `corepack pnpm nx dev:clean @zitadel/components` and hard-refresh.
142
+ Console (`:5174`) will not pick up Lit-only edits until you rebuild or
143
+ change the paired React/CSS path.
144
+
145
+ ### Atoms-only (bypass the orchestrator)
146
+
147
+ If you want a fully bespoke shell, use the atoms directly. You give up the
148
+ template, transitions, and focus management; you keep the styled, accessible,
149
+ form-associated inputs.
150
+
151
+ ```html
152
+ <form id="login">
153
+ <zl-field name="email" label="Email" type="email" autocomplete="username" required></zl-field>
154
+ <zl-button hierarchy="primary" type="submit" action="submit" label="Continue" block></zl-button>
155
+ </form>
156
+ ```
157
+
158
+ ## Customisation tiers
159
+
160
+ | Tier | Surface | Use when |
161
+ | --- | --- | --- |
162
+ | API base | `setProxyPath()` from `@zitadel/api/runtime/base-url` | every consumer — points at your backend |
163
+ | Tokens | branding payload returned from the server | tenant colour / logo / font |
164
+ | CSS hooks | `zitadel-login::part(form)`, `zl-field::part(input)` | targeted overrides from the host page |
165
+ | Locale | `el.locale = { ... }` | i18n / custom copy |
166
+ | MSW mocks | `setupWorker` / `setupServer` from `msw` | offline / staging / fixtures |
167
+ | Custom template | (planned) | tenant-supplied Liquid layouts |
168
+ | Atoms-only | hand-built form | non-standard flow shells |
169
+
170
+ The "Custom template" surface is not yet exposed on `<zitadel-login>`; the
171
+ orchestrator currently uses the bundled `auth_form.liquid`. Tracked as a
172
+ follow-up.
173
+
174
+ ## Element APIs
175
+
176
+ ### `<zitadel-login>`
177
+
178
+ | Property | Type | Notes |
179
+ | --- | --- | --- |
180
+ | `purpose` | `'login' \| 'register' \| 'reset_password' \| string` | Which flow purpose to drive |
181
+ | `projectId` / `project-id` | `string` | Project / tenant id sent with `POST /flow` |
182
+ | `apiBase` / `api-base` | `string` | Optional declarative override for `setProxyPath()` |
183
+ | `sessionExchangePath` / `session-exchange-path` | `string` | Handoff exchange path (default `/sessions/exchange`, prefixed with `api-base`). Any other value is resolved from `location.origin` instead — use when exchange is rewritten separately (e.g. `/api/auth/exchange`) |
184
+ | `postSignInUrl` / `post-sign-in-url` | `string` | After `complete: "show"`, exchange the `handoff_token` at the configured exchange path and navigate here |
185
+ | `resumeFlowId` / `resume-flow-id` | `string` | Resume an existing flow handle instead of starting fresh |
186
+ | `locale` | `Record<string, string>` | i18n dictionary consumed by Liquid's `\| t` filter |
187
+
188
+ Events: `zitadel-flow-input`, `zitadel-flow-action`, `zitadel-flow-step`,
189
+ `zitadel-flow-complete`, `zitadel-flow-error`. The orchestrator exposes
190
+ `::part(form)` for tenant-side CSS hooks. Adopts design tokens and
191
+ `branding.font_url` into its shadow root on each update.
192
+
193
+ ### `<zitadel-logout>`
194
+
195
+ Session menu / sign-out control for embedded apps. Reads the
196
+ `__nextgen_display` cookie set during sign-in, calls `GET /auth/end-session`
197
+ through `api-base`, and clears the session. Uses the same token adoption as
198
+ `<zitadel-login>` (`applyBaseTokens` + optional `font_url` when hosted on a
199
+ page without global tokens).
200
+
201
+ | Property | Type | Notes |
202
+ | --- | --- | --- |
203
+ | `apiBase` / `api-base` | `string` | Proxied auth API prefix (e.g. `/__nextgen`) |
204
+ | `postSignOutUrl` / `post-sign-out-url` | `string` | Navigate here after sign-out |
205
+ | `clientId` / `client-id` | `string` | Optional OIDC client id forwarded to end-session |
206
+
207
+ Supports a light-DOM `<template>` slot for a fully custom menu; default UI is
208
+ the avatar trigger + dropdown.
209
+
210
+ ### `<zl-field>`
211
+
212
+ Form-associated input. Participates in the parent `<form>`, supports validity
213
+ state, restores on form reset, and forwards Enter to `form.requestSubmit()`.
214
+
215
+ | Property | Type | Notes |
216
+ | --- | --- | --- |
217
+ | `name` | `string` | Form key |
218
+ | `label` | `string` | Visible label (rendered above) |
219
+ | `type` | `'text' \| 'email' \| 'password' \| ...` | Native input type |
220
+ | `value` | `string` | Two-way bound; emits `zl-input` |
221
+ | `placeholder`, `autocomplete`, `pattern` | `string` | Forwarded |
222
+ | `required`, `disabled`, `invalid` | `boolean` | |
223
+ | `error` | `string` | Custom validity message |
224
+
225
+ Parts: `field`, `label`, `input`, `error`, `help`.
226
+
227
+ ### `<zl-button>`
228
+
229
+ Single button atom covering the full Figma matrix:
230
+
231
+ | Attribute | Values | Notes |
232
+ | --- | --- | --- |
233
+ | `hierarchy` | `primary \| secondary \| text` | Visual rank |
234
+ | `size` | `medium \| small` | Figma surface sizes |
235
+ | `type` | `submit \| button` | When `submit`, takes part in the host `<form>` |
236
+ | `action` | `string` | Forwarded with the `zl-submit` CustomEvent |
237
+ | `loading`, `disabled`, `block` | `boolean` | |
238
+ | `label` | `string` | Convenience text; default slot is also supported |
239
+
240
+ Emits `zl-submit` for both primary submits and secondary navigations — the
241
+ orchestrator switches on `type`/`action` to decide whether to advance the flow
242
+ or just notify.
243
+
244
+ ### `<zl-alert>`
245
+
246
+ Inline status message for the four Figma severities (`error \| success \|
247
+ warning \| info`). Renders the matching icon automatically; supports an
248
+ optional `heading` and default-slot body.
249
+
250
+ ### `<zl-icon>` / `<zl-pill>` / `<zl-card>` / `<zl-page-shell>`
251
+
252
+ Pure presentational atoms — icons render from a curated inline-SVG sprite,
253
+ pills carry the "Secured with Zitadel" attribution chip (and any tenant
254
+ alternative), and `<zl-card>` + `<zl-page-shell>` build the auth-screen
255
+ chrome from design tokens.
256
+
257
+ See [`src/atoms/`](src/atoms) for full TypeScript types and JSDoc.
258
+
259
+ ## Project layout
260
+
261
+ ```
262
+ packages/components/
263
+ ├── dev/ Vite playground (atoms + login routes)
264
+ │ ├── index.html
265
+ │ ├── main.ts bootstraps @zitadel/api-mock + MSW worker
266
+ │ ├── branding-presets.ts tenant-style branding payloads
267
+ │ └── pages/ atom playground + <zitadel-login> demo
268
+ ├── src/
269
+ │ ├── atoms/ zl-field, zl-button, zl-alert, zl-icon, zl-pill,
270
+ │ │ zl-card, zl-page-shell + tests
271
+ │ ├── orchestrator/ <zitadel-login>, <zitadel-logout>, api-client, liquid, branding
272
+ │ │ ├── locales/ bundled English fallback
273
+ │ │ └── templates/ auth-form / passkey-upsell / signed-in liquid partials
274
+ │ ├── tokens/ re-export of @zitadel/design-tokens
275
+ │ ├── styles/ shared host styles, focus ring, t() css-var bridge
276
+ │ ├── manifests.ts per-atom attribute / part / event manifests
277
+ │ └── index.ts barrel
278
+ ├── tsdown.config.ts library build (externalises lit/liquidjs/dompurify)
279
+ ├── vite.config.mts dev server
280
+ └── vitest.config.ts jsdom (unit) + chromium (browser) projects
281
+ ```
282
+
283
+ ## Develop
284
+
285
+ Use **Nx** for tasks in this monorepo (`corepack pnpm nx <target> <project>`).
286
+ It matches CI caching and dependency order. Equivalent `pnpm --filter …` scripts
287
+ still exist on some packages, but Nx is the documented path.
288
+
289
+ ```sh
290
+ # install once at the repo root
291
+ corepack pnpm install
292
+
293
+ # --- Playgrounds (two terminals) ---
294
+
295
+ # Lit: atoms + login, in-browser MSW
296
+ corepack pnpm nx dev @zitadel/components
297
+ # → http://localhost:5173/?route=login
298
+ # → http://localhost:5173/?route=atoms
299
+
300
+ # React console: ui-react atom playground (compare to Lit ?route=atoms in another tab)
301
+ corepack pnpm nx dev @zitadel/console
302
+ # → http://localhost:5174
303
+
304
+ # Framework demos (TCP mock + SDK) — see apps/demo-*/README.md
305
+ # corepack pnpm nx start @zitadel/api-mock # → http://localhost:4000
306
+ # ZITADEL_URL=http://localhost:4000 corepack pnpm nx dev @zitadel/demo-next # → :3002
307
+ # ZITADEL_URL=http://localhost:4000 corepack pnpm nx dev @zitadel/demo-nuxt # → :3001
308
+
309
+ # --- Package checks ---
310
+
311
+ corepack pnpm nx test @zitadel/components
312
+ corepack pnpm nx test:browser @zitadel/components
313
+ corepack pnpm nx typecheck @zitadel/components
314
+ corepack pnpm nx build @zitadel/components
315
+ ```
316
+
317
+ The components dev server imports source TS from `src/` and hot-reloads on edits.
318
+ Run `build` when testing the published `dist/` shape (demos and npm consumers).
319
+
320
+ **Framework demos** need the TCP mock plus a rebuild after orchestrator changes —
321
+ see [`apps/demo-next/README.md`](../../apps/demo-next/README.md) and
322
+ [`apps/demo-nuxt/README.md`](../../apps/demo-nuxt/README.md).
323
+
324
+ ## Testing strategy
325
+
326
+ Tests are split across two Vitest projects:
327
+
328
+ - **`unit`** — `jsdom`, fast feedback for rendering, Liquid wiring, branding
329
+ validation, sanitiser allowlists, and DOM-level a11y attributes.
330
+ - **`browser`** — real Chromium via Playwright, covers behaviour `jsdom` can't
331
+ fake: `ElementInternals` / form-associated state, native form submit
332
+ interception, focus delegation across shadow roots.
333
+
334
+ Files ending in `.browser.spec.ts` run only in the browser project; everything
335
+ else runs in jsdom.
336
+
337
+ ## Related design docs
338
+
339
+ - [`docs/design/branding/`](../../docs/design/branding/) — token spec, theme,
340
+ template chrome, **and the form-participation ADR**.
341
+ - [`docs/design/flowengine/`](../../docs/design/flowengine/) — flow engine,
342
+ step shape, template security, visualizer.
343
+
344
+ ## License
345
+
346
+ MIT — see [LICENSE](../../LICENSE).
@@ -0,0 +1,2 @@
1
+ import { Bt as zlAlertManifest, Ft as ZlCard, Ht as ZlIcon, It as zlCardManifest, Lt as ZlButton, Rt as zlButtonManifest, Ut as zlIconManifest, Vt as IconName, a as ZlPasskeyResultDetail, c as zlPageShellManifest, d as zlFieldManifest, i as ZlPasskeyErrorDetail, l as ZlField, n as zlPillManifest, o as zlPasskeyManifest, r as ZlPasskey, s as ZlPageShell, t as ZlPill, u as ZlFieldType, zt as ZlAlert } from "../index-DIUFZjXw.mjs";
2
+ export { IconName, ZlAlert, ZlButton, ZlCard, ZlField, ZlFieldType, ZlIcon, ZlPageShell, ZlPasskey, ZlPasskeyErrorDetail, ZlPasskeyResultDetail, ZlPill, zlAlertManifest, zlButtonManifest, zlCardManifest, zlFieldManifest, zlIconManifest, zlPageShellManifest, zlPasskeyManifest, zlPillManifest };
@@ -0,0 +1,9 @@
1
+ export { ZlAlert, zlAlertManifest } from "./zl-alert.js";
2
+ export { ZlButton, zlButtonManifest } from "./zl-button.js";
3
+ export { ZlCard, zlCardManifest } from "./zl-card.js";
4
+ export { ZlField, zlFieldManifest, type ZlFieldType } from "./zl-field.js";
5
+ export { ZlIcon, zlIconManifest, type IconName } from "./zl-icon.js";
6
+ export { ZlPageShell, zlPageShellManifest } from "./zl-page-shell.js";
7
+ export { ZlPasskey, zlPasskeyManifest, type ZlPasskeyResultDetail, type ZlPasskeyErrorDetail } from "./zl-passkey.js";
8
+ export { ZlPill, zlPillManifest } from "./zl-pill.js";
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/atoms/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACzD,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAC5D,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACtD,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,KAAK,WAAW,EAAE,MAAM,eAAe,CAAC;AAC3E,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,KAAK,QAAQ,EAAE,MAAM,cAAc,CAAC;AACrE,OAAO,EAAE,WAAW,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACtE,OAAO,EAAE,SAAS,EAAE,iBAAiB,EAAE,KAAK,qBAAqB,EAAE,KAAK,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AACtH,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC"}
@@ -0,0 +1,2 @@
1
+ import { a as ZlPageShell, c as zlFieldManifest, d as ZlButton, f as zlButtonManifest, g as zlIconManifest, h as ZlIcon, i as zlPasskeyManifest, l as ZlCard, m as zlAlertManifest, n as zlPillManifest, o as zlPageShellManifest, p as ZlAlert, r as ZlPasskey, s as ZlField, t as ZlPill, u as zlCardManifest } from "../atoms-CmOTltTH.mjs";
2
+ export { ZlAlert, ZlButton, ZlCard, ZlField, ZlIcon, ZlPageShell, ZlPasskey, ZlPill, zlAlertManifest, zlButtonManifest, zlCardManifest, zlFieldManifest, zlIconManifest, zlPageShellManifest, zlPasskeyManifest, zlPillManifest };
@@ -0,0 +1,56 @@
1
+ import { LitElement } from "lit";
2
+ import type { AtomManifest } from "../manifest.js";
3
+ import "./zl-icon.js";
4
+ /**
5
+ * Atom: `<zl-alert>` — inline status message replacing the legacy
6
+ * `<zl-error>`.
7
+ *
8
+ * Spec lineage (file `8UjCXw8yemgljmbkWGrSfE`):
9
+ * - design-system master: node `6593:2640` (Alert/Error). Screen instance
10
+ * `6596:132779` matches the same chrome. Severity is conveyed by icon
11
+ * shape and colour, not by tinting the background.
12
+ *
13
+ * Per-state Figma values:
14
+ *
15
+ * Layout
16
+ * surface bg surface.default-primary-gray (#252528), no border
17
+ * padding 16px (spacing-03)
18
+ * gap 16px between icon and content
19
+ * radius 12px (radius.m)
20
+ * shadow 0 1px 2px rgba(16,24,40,0.05) (shadow-xs)
21
+ *
22
+ * Icon (16px)
23
+ * error alert-circle, color text.error (#ea4f70)
24
+ * warning alert-circle, color text.subtitle-orange (#f25543)
25
+ * success check, color text.success (#33a779)
26
+ * info info, color text.subtitle-gray (#bfbfcf)
27
+ *
28
+ * Text
29
+ * heading Arimo SemiBold 14/20, color text.button-invert (#f4f4f6)
30
+ * body Arimo Regular 14/20, color text.button-invert
31
+ * gap (heading→body) 4px (spacing-01)
32
+ *
33
+ * Close button (when dismissible)
34
+ * 36 × 36 hit target, padding 8px, radius 8px (radius.s)
35
+ * icon `cross` at 20px, color text.secondary-gray
36
+ * placed at the end of the flex row (icon · content · close)
37
+ *
38
+ * Templates render one per step when the flow returns errors; the
39
+ * orchestrator surfaces flow-level errors through `errors` in the Liquid
40
+ * context.
41
+ */
42
+ export declare class ZlAlert extends LitElement {
43
+ static styles: import("lit").CSSResult[];
44
+ accessor severity: "error" | "success" | "warning" | "info";
45
+ accessor heading: string | undefined;
46
+ accessor dismissible: boolean;
47
+ render(): import("lit").TemplateResult<1>;
48
+ private handleDismiss;
49
+ }
50
+ export declare const zlAlertManifest: AtomManifest;
51
+ declare global {
52
+ interface HTMLElementTagNameMap {
53
+ "zl-alert": ZlAlert;
54
+ }
55
+ }
56
+ //# sourceMappingURL=zl-alert.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"zl-alert.d.ts","sourceRoot":"","sources":["../../src/atoms/zl-alert.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAQ,MAAM,KAAK,CAAC;AAMvC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAGnD,OAAO,cAAc,CAAC;AAGtB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,qBACa,OAAQ,SAAQ,UAAU;IACrC,OAAgB,MAAM,4BAGpB;IAE2B,QAAQ,CAAC,QAAQ,EAAE,OAAO,GAAG,SAAS,GAAG,SAAS,GAAG,MAAM,CAAW;IAEvF,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,CAAa;IAEjB,QAAQ,CAAC,WAAW,UAAS;IAEhE,MAAM,oCA6Bd;IAED,OAAO,CAAC,aAAa,CAKnB;CACH;AASD,eAAO,MAAM,eAAe,EAAE,YAMpB,CAAC;AAEX,OAAO,CAAC,MAAM,CAAC,CAAC;IACd,UAAU,qBAAqB;QAC7B,UAAU,EAAE,OAAO,CAAC;KACrB;CACF"}
@@ -0,0 +1,83 @@
1
+ import { LitElement } from "lit";
2
+ import type { AtomManifest } from "../manifest.js";
3
+ import "./zl-icon.js";
4
+ /**
5
+ * Atom: `<zl-button>` — the entire Figma button matrix in a single atom.
6
+ *
7
+ * Spec lineage (file `8UjCXw8yemgljmbkWGrSfE`, "Zitadel - Design System - External"):
8
+ * - master variant set: node `6598:292`
9
+ * - icon-leading instance reference: node `6598:143281` (passkey upsell)
10
+ *
11
+ * Variant axes (matches Figma directly):
12
+ * - hierarchy: primary | secondary | text
13
+ * - size: medium (48 × auto) | small (40 × auto)
14
+ * - state: enabled / hovered / focused / pressed / disabled
15
+ * (derived from interaction, not props)
16
+ * - slots: `leading` icon, `trailing` icon, default for the label.
17
+ * When `loading` is set the trailing slot is replaced with a
18
+ * spinning ring (Figma shows the spinner trailing).
19
+ *
20
+ * Per-state Figma values (resolved from `figma.tokens.json` primitives):
21
+ *
22
+ * Primary
23
+ * enabled bg=#f4f4f6 (surface.default-white) fg=#0f0f11 border=transparent
24
+ * hover/pressed bg=#cfcfde (gray.600) fg=#0f0f11 border=1px #252528
25
+ * focus-visible bg=#cfcfde + native outline ring
26
+ * disabled bg=#f4f4f6 fg=#686883 (text.disabled)
27
+ *
28
+ * Secondary
29
+ * enabled bg=#252528 (border.default-black) fg=#f4f4f6 (text.button-invert)
30
+ * hover/pressed bg=#484a57 (gray.200) fg=#f4f4f6
31
+ * focus-visible bg=#252528 + native outline ring fg=#f4f4f6
32
+ * disabled bg=#252528 fg=#686883
33
+ *
34
+ * Text
35
+ * enabled bg=transparent fg=#f4f4f6
36
+ * hover/pressed bg=#484a57 fg=#f4f4f6
37
+ * focus-visible bg=#252528 + native outline ring fg=#f4f4f6
38
+ * disabled bg=transparent fg=#686883
39
+ *
40
+ * Common
41
+ * padding 10px (block) / 16px (inline) radius=8px
42
+ * gap 8px between icon/label/icon
43
+ * font Arimo SemiBold (600)
44
+ * medium: 16/24 small: 14/20
45
+ * icon 24px medium, 16px small
46
+ * focus ring 2px solid #f4f4f6, 2px offset
47
+ * transition background-color / color / border-color, fast standard
48
+ *
49
+ * Form-associated so it can participate in the orchestrator's `<form>`
50
+ * exactly like a native `<button>`. Setting `type="submit"` triggers
51
+ * `form.requestSubmit()` on click; setting `type="reset"` calls
52
+ * `form.reset()`. Otherwise the click event bubbles for SPA listeners.
53
+ *
54
+ * The atom always renders a real `<button>` inside the shadow root —
55
+ * `delegatesFocus` ensures Tab lands on it and `<zl-field>`'s
56
+ * Enter-to-submit pipe still fires the host form.
57
+ */
58
+ export declare class ZlButton extends LitElement {
59
+ static formAssociated: boolean;
60
+ static shadowRootOptions: ShadowRootInit;
61
+ static styles: import("lit").CSSResult[];
62
+ accessor hierarchy: "primary" | "secondary" | "text";
63
+ accessor size: "medium" | "small";
64
+ accessor type: "button" | "submit" | "reset";
65
+ accessor action: string | undefined;
66
+ accessor loading: boolean;
67
+ accessor disabled: boolean;
68
+ accessor block: boolean;
69
+ accessor label: string | undefined;
70
+ private readonly internals;
71
+ constructor();
72
+ focus(options?: FocusOptions): void;
73
+ private surfaceClasses;
74
+ render(): import("lit").TemplateResult<1>;
75
+ private handleClick;
76
+ }
77
+ export declare const zlButtonManifest: AtomManifest;
78
+ declare global {
79
+ interface HTMLElementTagNameMap {
80
+ "zl-button": ZlButton;
81
+ }
82
+ }
83
+ //# sourceMappingURL=zl-button.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"zl-button.d.ts","sourceRoot":"","sources":["../../src/atoms/zl-button.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAiB,MAAM,KAAK,CAAC;AAMhD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAGnD,OAAO,cAAc,CAAC;AAEtB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqDG;AACH,qBACa,QAAS,SAAQ,UAAU;IACtC,MAAM,CAAC,cAAc,UAAQ;IAE7B,OAAgB,iBAAiB,EAAE,cAAc,CAG/C;IAEF,OAAgB,MAAM,4BAGpB;IAE2B,QAAQ,CAAC,SAAS,EAAE,SAAS,GAAG,WAAW,GAAG,MAAM,CAAa;IAEjE,QAAQ,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAY;IAE9D,QAAQ,CAAC,IAAI,EAAE,QAAQ,GAAG,QAAQ,GAAG,OAAO,CAAY;IAExD,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAa;IAE/B,QAAQ,CAAC,OAAO,UAAS;IAEV,QAAQ,CAAC,QAAQ,UAAS;IAE1B,QAAQ,CAAC,KAAK,UAAS;IAEvD,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,CAAa;IAE3D,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAmB;IAE7C,cAGC;IAEQ,KAAK,CAAC,OAAO,CAAC,EAAE,YAAY,GAAG,IAAI,CAE3C;IAED,OAAO,CAAC,cAAc;IAYb,MAAM,oCAyBd;IAED,OAAO,CAAC,WAAW,CAuBjB;CACH;AAED,eAAO,MAAM,gBAAgB,EAAE,YAOrB,CAAC;AAEX,OAAO,CAAC,MAAM,CAAC,CAAC;IACd,UAAU,qBAAqB;QAC7B,WAAW,EAAE,QAAQ,CAAC;KACvB;CACF"}
@@ -0,0 +1,46 @@
1
+ import { LitElement } from "lit";
2
+ import type { AtomManifest } from "../manifest.js";
3
+ /**
4
+ * Atom: `<zl-card>` — the auth-card surface used by every flow screen.
5
+ *
6
+ * Spec lineage (file `xkvBjkOJ8ENuHdTGZHXezK`):
7
+ * - sign-in: node `6593:141985` (combined sign-in card, 32px gap)
8
+ * - sign-up: node `6593:141743` / frame `6593:141741` (title only, 32px gap)
9
+ * - passkey-upsell: node `6594:632` (288h, heading+subtitle, 24px gap)
10
+ * - signed-in: node `6596:132846` (272h, heading+subtitle, 24px gap)
11
+ *
12
+ * Per-state Figma values (constant across all four screens unless noted):
13
+ *
14
+ * width 384px fixed (container.auth-card token; xs collapses
15
+ * to full-width via the surrounding page-shell)
16
+ * background transparent — the page (surface.default-black) shows
17
+ * through; the card is *not* an elevated surface, only
18
+ * outlined
19
+ * border 1px solid surface.default-primary-gray (#252528)
20
+ * radius 16px (radius.l)
21
+ * padding 32px all sides (spacing-05)
22
+ * gap 32px between header / body / footer slots (sign-in,
23
+ * sign-up). Passkey-upsell + signed-in tighten to 24px
24
+ * (spacing-04) via `compact` because their heading group
25
+ * carries a subtitle.
26
+ *
27
+ * Internal slot gaps:
28
+ * header 8px (spacing-02) — heading → subtitle stack
29
+ * body 16px (spacing-03) — fields/buttons stack (matches
30
+ * Figma "Field+CTAs" gap)
31
+ * footer 8px (spacing-02) — auxiliary links
32
+ */
33
+ export declare class ZlCard extends LitElement {
34
+ static styles: import("lit").CSSResult[];
35
+ accessor compact: boolean;
36
+ render(): import("lit").TemplateResult<1>;
37
+ /** Slotted nodes are always light-DOM children of the host in our templates. */
38
+ private lightDomSlotFilled;
39
+ }
40
+ export declare const zlCardManifest: AtomManifest;
41
+ declare global {
42
+ interface HTMLElementTagNameMap {
43
+ "zl-card": ZlCard;
44
+ }
45
+ }
46
+ //# sourceMappingURL=zl-card.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"zl-card.d.ts","sourceRoot":"","sources":["../../src/atoms/zl-card.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAQ,MAAM,KAAK,CAAC;AAMvC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAGnD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,qBACa,MAAO,SAAQ,UAAU;IACpC,OAAgB,MAAM,4BAA6D;IAEvC,QAAQ,CAAC,OAAO,UAAS;IAE5D,MAAM,oCAmBd;IAED,gFAAgF;IAChF,OAAO,CAAC,kBAAkB;CAM3B;AAED,eAAO,MAAM,cAAc,EAAE,YAMnB,CAAC;AAEX,OAAO,CAAC,MAAM,CAAC,CAAC;IACd,UAAU,qBAAqB;QAC7B,SAAS,EAAE,MAAM,CAAC;KACnB;CACF"}
@@ -0,0 +1,73 @@
1
+ import type { CreateFlow201StepFieldsType } from "@zitadel/api/generated/model";
2
+ import { LitElement, type PropertyValues } from "lit";
3
+ import type { AtomManifest } from "../manifest.js";
4
+ import "./zl-icon.js";
5
+ /**
6
+ * Alias of the orval-generated wire enum for field types so the atom's
7
+ * accepted `type` values track the API contract exactly.
8
+ */
9
+ export type ZlFieldType = CreateFlow201StepFieldsType;
10
+ /**
11
+ * Atom: `<zl-field>` — labelled input bound to a step `field`. Visual
12
+ * spec lineage:
13
+ * - design-system master variant set: node `4390:1404` (file
14
+ * `8UjCXw8yemgljmbkWGrSfE`) — Enabled / Hovered / Focused / Filled /
15
+ * Disabled / Error / Success / Password forgot
16
+ *
17
+ * Per-state Figma values (master `4390:1404`):
18
+ *
19
+ * Layout
20
+ * root flex-column, gap=8px (spacing-02)
21
+ * label row optional "Forgot password?" link (#bba5e4, 14/20)
22
+ * input-wrap 40px, padding 8/16, trailing icon at right 15px
23
+ * trailing cross (clear) | alert-circle (error) | check (success)
24
+ *
25
+ * Form participation: `<zl-field>` is a form-associated custom element.
26
+ */
27
+ export declare class ZlField extends LitElement {
28
+ static formAssociated: boolean;
29
+ static shadowRootOptions: ShadowRootInit;
30
+ static styles: import("lit").CSSResult[];
31
+ accessor name: string;
32
+ accessor label: string;
33
+ accessor type: ZlFieldType;
34
+ accessor value: string;
35
+ accessor placeholder: string;
36
+ accessor autocomplete: string | undefined;
37
+ accessor pattern: string | undefined;
38
+ accessor error: string;
39
+ accessor success: string;
40
+ accessor forgotPasswordHref: string | undefined;
41
+ accessor forgotPasswordLabel: string;
42
+ accessor trailingIcon: boolean;
43
+ accessor required: boolean;
44
+ accessor disabled: boolean;
45
+ accessor invalid: boolean;
46
+ private accessor hasHelp;
47
+ private accessor hasSuffixSlot;
48
+ private readonly inputId;
49
+ private readonly internals;
50
+ constructor();
51
+ connectedCallback(): void;
52
+ willUpdate(changed: PropertyValues<this>): void;
53
+ formResetCallback(): void;
54
+ formStateRestoreCallback(state: string | null): void;
55
+ focus(options?: FocusOptions): void;
56
+ render(): import("lit").TemplateResult<1>;
57
+ private renderLabelRow;
58
+ private renderTrailingIcon;
59
+ private syncFormState;
60
+ private handleHelpSlotChange;
61
+ private handleSuffixSlotChange;
62
+ private handleClear;
63
+ private handleInput;
64
+ private handleChange;
65
+ private handleKeyDown;
66
+ }
67
+ export declare const zlFieldManifest: AtomManifest;
68
+ declare global {
69
+ interface HTMLElementTagNameMap {
70
+ "zl-field": ZlField;
71
+ }
72
+ }
73
+ //# sourceMappingURL=zl-field.d.ts.map