@urbicon-ui/design-content 6.1.8

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 (111) hide show
  1. package/README.md +54 -0
  2. package/content/auth/components/account-settings/llm.txt +33 -0
  3. package/content/auth/components/forgot-password-page/llm.txt +25 -0
  4. package/content/auth/components/invitation-manager/llm.txt +28 -0
  5. package/content/auth/components/login-page/llm.txt +34 -0
  6. package/content/auth/components/notification-badge/llm.txt +21 -0
  7. package/content/auth/components/notification-center/llm.txt +33 -0
  8. package/content/auth/components/notification-listener/llm.txt +24 -0
  9. package/content/auth/components/passkey-manager/llm.txt +27 -0
  10. package/content/auth/components/push-permission-prompt/llm.txt +32 -0
  11. package/content/auth/components/register-page/llm.txt +35 -0
  12. package/content/auth/components/reset-password-page/llm.txt +26 -0
  13. package/content/auth/components/session-manager/llm.txt +32 -0
  14. package/content/auth/components/two-factor-manager/llm.txt +40 -0
  15. package/content/auth/components/verify-email-page/llm.txt +25 -0
  16. package/content/blocks/components/area-chart/llm.txt +46 -0
  17. package/content/blocks/components/bar-chart/llm.txt +44 -0
  18. package/content/blocks/components/calendar/llm.txt +105 -0
  19. package/content/blocks/components/chart-frame/llm.txt +38 -0
  20. package/content/blocks/components/command-palette/llm.txt +60 -0
  21. package/content/blocks/components/composition-bar/llm.txt +69 -0
  22. package/content/blocks/components/currency-input/llm.txt +65 -0
  23. package/content/blocks/components/date-picker/llm.txt +90 -0
  24. package/content/blocks/components/donut-chart/llm.txt +45 -0
  25. package/content/blocks/components/empty-state/llm.txt +43 -0
  26. package/content/blocks/components/file-upload/llm.txt +76 -0
  27. package/content/blocks/components/guide/llm.txt +49 -0
  28. package/content/blocks/components/guide-article/llm.txt +30 -0
  29. package/content/blocks/components/guide-beacon/llm.txt +38 -0
  30. package/content/blocks/components/guide-hint/llm.txt +41 -0
  31. package/content/blocks/components/guide-marker/llm.txt +36 -0
  32. package/content/blocks/components/guide-mention/llm.txt +31 -0
  33. package/content/blocks/components/guide-panel/llm.txt +42 -0
  34. package/content/blocks/components/guide-provider/llm.txt +31 -0
  35. package/content/blocks/components/line-chart/llm.txt +45 -0
  36. package/content/blocks/components/locale-switcher/llm.txt +44 -0
  37. package/content/blocks/components/planner/llm.txt +68 -0
  38. package/content/blocks/components/sankey/llm.txt +72 -0
  39. package/content/blocks/components/sidebar-layout/llm.txt +87 -0
  40. package/content/blocks/components/sparkline/llm.txt +33 -0
  41. package/content/blocks/components/theme-switcher/llm.txt +40 -0
  42. package/content/blocks/primitives/accordion/llm.txt +57 -0
  43. package/content/blocks/primitives/alert/llm.txt +54 -0
  44. package/content/blocks/primitives/avatar/llm.txt +61 -0
  45. package/content/blocks/primitives/badge/llm.txt +60 -0
  46. package/content/blocks/primitives/breadcrumb/llm.txt +47 -0
  47. package/content/blocks/primitives/button/llm.txt +80 -0
  48. package/content/blocks/primitives/button-group/llm.txt +65 -0
  49. package/content/blocks/primitives/card/llm.txt +68 -0
  50. package/content/blocks/primitives/checkbox/llm.txt +61 -0
  51. package/content/blocks/primitives/collapsible/llm.txt +66 -0
  52. package/content/blocks/primitives/combobox/llm.txt +86 -0
  53. package/content/blocks/primitives/confirm-dialog/llm.txt +47 -0
  54. package/content/blocks/primitives/dialog/llm.txt +59 -0
  55. package/content/blocks/primitives/drawer/llm.txt +54 -0
  56. package/content/blocks/primitives/form-field/llm.txt +43 -0
  57. package/content/blocks/primitives/input/llm.txt +73 -0
  58. package/content/blocks/primitives/menu/llm.txt +81 -0
  59. package/content/blocks/primitives/pagination/llm.txt +68 -0
  60. package/content/blocks/primitives/popover/llm.txt +72 -0
  61. package/content/blocks/primitives/progress/llm.txt +55 -0
  62. package/content/blocks/primitives/radio-group/llm.txt +53 -0
  63. package/content/blocks/primitives/segment-group/llm.txt +51 -0
  64. package/content/blocks/primitives/select/llm.txt +130 -0
  65. package/content/blocks/primitives/separator/llm.txt +45 -0
  66. package/content/blocks/primitives/sidebar/llm.txt +79 -0
  67. package/content/blocks/primitives/skeleton/llm.txt +54 -0
  68. package/content/blocks/primitives/slider/llm.txt +82 -0
  69. package/content/blocks/primitives/spinner/llm.txt +46 -0
  70. package/content/blocks/primitives/stepper/llm.txt +60 -0
  71. package/content/blocks/primitives/tab/llm.txt +72 -0
  72. package/content/blocks/primitives/textarea/llm.txt +61 -0
  73. package/content/blocks/primitives/toast/llm.txt +45 -0
  74. package/content/blocks/primitives/toggle/llm.txt +62 -0
  75. package/content/blocks/primitives/toolbar/llm.txt +60 -0
  76. package/content/blocks/primitives/tooltip/llm.txt +54 -0
  77. package/content/component-catalog.json +5010 -0
  78. package/content/design-system/patterns/dashboard.md +55 -0
  79. package/content/design-system/patterns/form-page.md +69 -0
  80. package/content/design-system/patterns/onboarding-guide.md +50 -0
  81. package/content/design-system/patterns/planning-board.md +46 -0
  82. package/content/design-system/patterns/settings-page.md +48 -0
  83. package/content/design-system/patterns/tab-navigation.md +136 -0
  84. package/content/design-system/principles.md +260 -0
  85. package/content/docs/components/api-reference/llm.txt +32 -0
  86. package/content/docs/components/code-example/llm.txt +44 -0
  87. package/content/docs/components/code-panel/llm.txt +26 -0
  88. package/content/docs/components/docs-layout/llm.txt +61 -0
  89. package/content/docs/components/info-card/llm.txt +31 -0
  90. package/content/docs/components/playground-configurator/llm.txt +49 -0
  91. package/content/docs/components/section/llm.txt +46 -0
  92. package/content/docs/components/table-of-contents/llm.txt +48 -0
  93. package/content/docs/components/types-reference/llm.txt +38 -0
  94. package/content/guides/llms-full-template.md +1019 -0
  95. package/content/icons.json +4834 -0
  96. package/content/meta.json +5 -0
  97. package/content/table/table/llm.txt +110 -0
  98. package/content/verbs/adopt.md +33 -0
  99. package/content/verbs/audit.md +29 -0
  100. package/content/verbs/compose.md +38 -0
  101. package/content/verbs/critique.md +27 -0
  102. package/content/verbs/fix.md +29 -0
  103. package/content/verbs/migrate.md +30 -0
  104. package/content/verbs/onboard.md +33 -0
  105. package/content/verbs/polish.md +25 -0
  106. package/content/verbs/redesign.md +29 -0
  107. package/content/verbs/retheme.md +29 -0
  108. package/package.json +45 -0
  109. package/src/content-loader.test.ts +78 -0
  110. package/src/content-loader.ts +97 -0
  111. package/src/index.ts +23 -0
@@ -0,0 +1,1019 @@
1
+ # Urbicon UI – Full API Reference for LLMs
2
+
3
+ > This file is optimized for LLM consumption. It contains everything needed to correctly generate code using Urbicon UI.
4
+
5
+ ## Installation
6
+
7
+ Urbicon UI packages are published to a private registry. Create a `bunfig.toml` in your project root:
8
+
9
+ ```toml
10
+ # bunfig.toml
11
+ [install.scopes]
12
+ "@urbicon-ui" = { url = "https://npm.urbicon.de/" }
13
+ ```
14
+
15
+ Then install the packages:
16
+
17
+ ```bash
18
+ bun add @urbicon-ui/blocks @urbicon-ui/i18n
19
+ ```
20
+
21
+ CSS setup (in your app's root layout or entry CSS). Your app owns the Tailwind
22
+ import and it MUST come first; the Urbicon UI CSS that follows depends on it and
23
+ overrides its defaults:
24
+ ```css
25
+ @import 'tailwindcss';
26
+ @import '@urbicon-ui/blocks/style/index.css'; /* tokens + @source directives */
27
+ @import '@urbicon-ui/table/style/index.css'; /* only if you use @urbicon-ui/table */
28
+ ```
29
+
30
+ Import the single `style/index.css` — **not** the `foundation`/`semantic`/`interaction`
31
+ subfiles. `index.css` ships the design tokens, global classes (`.sr-only`, mint styles),
32
+ and the Tailwind `@source` directives that make Tailwind scan the component classes inside
33
+ `node_modules`. You do **not** need to add any manual `@source` directives; importing the
34
+ subfiles (which omit those directives) is the usual cause of responsive utilities like
35
+ `lg:hidden` going missing in production.
36
+
37
+ ## Import Pattern
38
+
39
+ ALWAYS import from the package root:
40
+ ```svelte
41
+ <script>
42
+ import { Button, Input, Card, Dialog, Badge } from '@urbicon-ui/blocks';
43
+ </script>
44
+ ```
45
+
46
+ NEVER import from internal paths like `@urbicon-ui/blocks/primitives/Button`.
47
+
48
+ ---
49
+
50
+ ## Shared API Grammar
51
+
52
+ Every component follows a predictable API. Learn once, apply everywhere.
53
+
54
+ ### `intent` – Semantic Color
55
+
56
+ Values: `primary` | `secondary` | `success` | `warning` | `danger` | `neutral`
57
+ Default: `neutral` (action elements), `primary` (form/decorative elements)
58
+
59
+ ### `variant` – Visual Weight
60
+
61
+ Values: `filled` | `outlined` | `ghost` | `text` (component-dependent)
62
+ - `filled`: Solid background, highest emphasis
63
+ - `outlined`: Border only, transparent background
64
+ - `ghost`: No border, no background
65
+ - `text`: Minimal, text-only (Button only)
66
+
67
+ ### `size` – Dimensions
68
+
69
+ Standard scale: `xs` | `sm` | `md` | `lg` | `xl`
70
+ Default: `md` for all components.
71
+
72
+ | Size | Height | Font |
73
+ |------|--------|----------|
74
+ | xs | h-6 | text-xs |
75
+ | sm | h-8 | text-sm |
76
+ | md | h-10 | text-base|
77
+ | lg | h-12 | text-lg |
78
+ | xl | h-14 | text-xl |
79
+
80
+ ### `unstyled` – Strip All Default Styles
81
+
82
+ ```svelte
83
+ <Button unstyled class="my-custom-classes">Custom</Button>
84
+ ```
85
+
86
+ ### `slotClasses` – Override Specific Slots
87
+
88
+ Each component documents its slots. Override without losing other styles:
89
+ ```svelte
90
+ <Card slotClasses={{ header: 'bg-primary-subtle', footer: 'border-t-2' }}>
91
+ ...
92
+ </Card>
93
+ ```
94
+
95
+ ### `mint` – Micro-Interactions (opt-in)
96
+
97
+ Requires `registerDefaultMints()` call at app startup.
98
+ Values: `'scale'` | `'ripple'` | `'translate'` | `'glow'` | `'none'` | array
99
+
100
+ ```svelte
101
+ <Button mint="scale">Hover me</Button>
102
+ <Card mint={['scale', 'glow']}>Interactive</Card>
103
+ ```
104
+
105
+ ### `preset` – Apply a Named Project Style
106
+
107
+ Values: any preset name registered via `<BlocksProvider presets={{...}}>` (project-specific).
108
+
109
+ Presets are the correct escape hatch when you need a look **outside the semantic intent palette**
110
+ (`primary | secondary | success | warning | danger | neutral`) — e.g. a dark translucent overlay
111
+ button on top of an image, a brand-colored button, or a "glass" surface. They keep hover/active/
112
+ dark-mode logic coherent AND make the custom look reusable across the project.
113
+
114
+ ```svelte
115
+ <Button preset="overlay">Reinholen</Button>
116
+ <Card preset="glass">...</Card>
117
+ ```
118
+
119
+ Registration happens once at the app root — see `Customization` → `Level 2: Presets` below.
120
+
121
+ **DO NOT** reach for `class="bg-…! hover:bg-…! active:bg-…!"` when the intent palette doesn't fit.
122
+ That pattern defeats the component's hover/active/dark-mode cascade and leaks visual decisions
123
+ into every call site. Register a preset instead.
124
+
125
+ ### `class` – Additional CSS Classes
126
+
127
+ Merged with variant classes. Always available on every component.
128
+
129
+ ---
130
+
131
+ ## Component Families
132
+
133
+ Every primitive belongs to exactly one of six families. The family decides ARIA role, tier-system membership, and border-token source. Pick the right family up-front to avoid categorical bugs (button that looks like an input, menu that doubles as a listbox, avatar that mutates when commit-radii flatten).
134
+
135
+ ### Family table
136
+
137
+ | Family | Members | ARIA role | Tier default | Border source |
138
+ |---|---|---|---|---|
139
+ | Action | Button, ButtonGroup, Menu, Toolbar, Toggle | `button`, `menu`, `menuitem`, `toolbar`, `switch` | `commit` (tier-aware) | Intent (`border-neutral` etc.) |
140
+ | Form | Input, Select, Combobox, Textarea, Checkbox, RadioGroup, Slider, FormField | `textbox`, `listbox`, `combobox`, `checkbox`, `radio`, `slider` | `modify` (tier-aware) | Surface (`border-border-subtle`) |
141
+ | Navigation | Breadcrumb, Pagination, SegmentGroup, Stepper, Tab | `navigation`, `tablist`, `tab` | per-component (tier-aware) | mixed |
142
+ | Container | Card, Alert, Accordion, Collapsible, Dialog, Drawer, Popover, Tooltip, Sidebar, Separator, ConfirmDialog | `dialog`, `tooltip`, `region`, `aside` | `contain` (tier-aware) | Surface or Hairline |
143
+ | Feedback / Ambient | Toast, Spinner, Progress, Skeleton, Badge | `status`, `alert`, `progressbar` | **not tier-aware** (Badge is the documented edge case) | Intent (status-tinted) or none |
144
+ | Identity | Avatar | `img` or `button` | **not tier-aware** — own shape axis (`circle` / `rounded` / `square`) | none |
145
+
146
+ ### Action — interactive triggers
147
+
148
+ - ARIA: items dispatch `onSelect` / `onclick`; never hold a value.
149
+ - Border: must read as interactive even in `intent="neutral"` — uses Intent tokens (~neutral-500 in light).
150
+ - Industry analogue: Radix DropdownMenu, Headless UI Menu.
151
+ - Pick `Menu` for one-off action lists; `Button` for single triggers; `ButtonGroup` for grouped triggers; `Toolbar` for free-form bars; `Toggle` for bistable switches.
152
+
153
+ ### Form — value holders
154
+
155
+ - ARIA: control owns a value, emits `onValueChange` / `bind:value`.
156
+ - Border: must read as a container, not a button — uses Surface tokens (~neutral-200 in light).
157
+ - Industry analogue: Radix Select / Combobox, Headless UI Listbox / Combobox.
158
+ - Pick `Select` for value pickers; `Combobox` for searchable Select; `Select multiple` for multi-select (NOT `Menu multiple`); `Menu` (Action family) for one-off action lists.
159
+
160
+ ### Navigation — section / route selection
161
+
162
+ - ARIA: `<nav aria-label>`, `role="tablist"` + `role="tab"`, `aria-current` for breadcrumbs / pagination.
163
+ - Border: per-component; SegmentGroup indicator uses Intent (active item reads as action-like).
164
+ - Pick `Tab` for sectioned content; `SegmentGroup` for inline pickers (holds value, unlike ButtonGroup); `Stepper` for linear progress; `Breadcrumb` for route context; `Pagination` for list paging.
165
+
166
+ ### Container — content surfaces
167
+
168
+ - ARIA: `<dialog>`, `role="tooltip"`, `<aside>`, `<details>` / `aria-expanded`.
169
+ - Border: never Intent in default state — Surface or Hairline. If a container border reads as a button, family mismatch.
170
+ - Pick `Card` / `Alert` for in-page surfaces; `Dialog` / `Drawer` / `ConfirmDialog` for modal overlays; `Popover` for anchored floating; `Tooltip` for hover-described inline targets; `Sidebar` for persistent app-shell; `Accordion` / `Collapsible` for disclosure.
171
+
172
+ ### Feedback / Ambient — status communication
173
+
174
+ - ARIA: `role="alert"`, `role="status"`, `role="progressbar"`. Spinner inherits `aria-busy` from host.
175
+ - Tier: NOT tier-aware. Geometry is per-component (e.g. Spinner is always `rounded-full`, Toast is always `rounded-contain`). A wrapping `<Toolbar tier="modify">` does NOT flatten a Toast.
176
+ - Badge exception: Badge DOES expose a `tier` prop (`commit` default, `modify` opt-in for inline Toolbar strips) — but the family rule remains: Feedback geometry is per-component, not per-context.
177
+ - Pick `Toast` for system notifications; `Alert` for in-page banners; `Spinner` / `Progress` / `Skeleton` for loading states; `Badge` for status tags / counters (see Badge Patterns section for the 5 use cases).
178
+
179
+ ### Identity — Avatar only
180
+
181
+ - Avatar lives outside the tier system. Its `variant` axis (`circle` / `rounded` / `square`) is identity-shape, not layout-tier. A brand that flattens `--radius-commit` (squared pill buttons) keeps circular avatars.
182
+ - Avatar uses no border in its default render; `ring` is the only border-adjacent affordance.
183
+
184
+ ### Cross-family disambiguation
185
+
186
+ - `Menu` vs `Select` — Menu for one-off actions (Action), Select for value pickers (Form). Different ARIA, different border family.
187
+ - `ButtonGroup` vs `SegmentGroup` — ButtonGroup dispatches actions, SegmentGroup holds a value. If you `bind:value` on a ButtonGroup, switch to SegmentGroup.
188
+ - `Sidebar` vs `Drawer` — Sidebar for persistent layout (`<aside>`, no backdrop by default), Drawer for transient modal (`<dialog>`, always backdrop + focus-trap).
189
+ - `Popover` vs `Tooltip` — Popover hosts a focus-trapped panel for click-interactions, Tooltip is non-focusable for hover-descriptions.
190
+ - `Alert` vs `Toast` — Alert is in-page (`role="alert"`), Toast is system-level + stacking.
191
+
192
+ ### Tier-aware components (read context from `<TierContext>`)
193
+
194
+ Seven primitives expose a `tier` prop AND inherit from a wrapping context when unset:
195
+
196
+ | Component | Default tier | Family |
197
+ |---|---|---|
198
+ | Button | `commit` | Action |
199
+ | Toggle | `commit` | Action |
200
+ | SegmentGroup | `commit` | Navigation |
201
+ | Stepper | `commit` | Navigation |
202
+ | RadioGroup | `commit` | Form |
203
+ | Checkbox | `modify` | Form |
204
+ | Tab | `modify` | Navigation |
205
+
206
+ All other primitives use a fixed tier per family (see family table above) — Container components are `contain`, Form components are `modify`, Action components are `commit`, Feedback / Identity components are not tier-aware at all.
207
+
208
+ ---
209
+
210
+ ## Components
211
+
212
+ {{COMPONENTS}}
213
+
214
+ ---
215
+
216
+ ## Design Token System
217
+
218
+ This is the COMPLETE list of available semantic tokens. Use ONLY these — do not invent token names.
219
+
220
+ ### Surface Tokens (backgrounds)
221
+
222
+ ```
223
+ bg-surface-base /* page background */
224
+ bg-surface-quiet /* softly tinted in-page zone (Lighter default) */
225
+ bg-surface-subtle /* visible tinted zone */
226
+ bg-surface-elevated /* floating surfaces (paired with shadow) */
227
+ bg-surface-overlay /* modals, popovers */
228
+ bg-surface-interactive /* interactive backgrounds */
229
+ bg-surface-hover /* hover state */
230
+ bg-surface-active /* active/pressed state */
231
+ bg-surface-disabled /* disabled elements */
232
+ bg-surface-selected /* selected items (uses primary-50) */
233
+ bg-surface-inverted /* inverted surfaces (tooltips) */
234
+ ```
235
+
236
+ ### Text Tokens
237
+
238
+ ```
239
+ text-text-primary /* main text */
240
+ text-text-secondary /* supporting text */
241
+ text-text-tertiary /* muted text, metadata */
242
+ text-text-quaternary /* most subtle text */
243
+ text-text-disabled /* disabled text */
244
+ text-text-inverted /* text on inverted surfaces */
245
+ text-text-on-primary /* text on intent-colored backgrounds */
246
+ text-text-on-dark /* text on dark surfaces */
247
+ text-text-on-surface /* text on any surface (auto-contrast) */
248
+ ```
249
+
250
+ ### Border Tokens
251
+
252
+ ```
253
+ border-border-subtle /* gentle grouping */
254
+ border-border-default /* standard borders */
255
+ border-border-emphasis /* emphasized borders */
256
+ border-border-strong /* high-contrast borders */
257
+ ```
258
+
259
+ ### Intent Tokens (available for ALL intents: primary, secondary, neutral, success, warning, danger)
260
+
261
+ Each intent has 5 variants. Example with `success`:
262
+ ```
263
+ bg-success /* base intent color */
264
+ bg-success-hover /* hover state */
265
+ bg-success-active /* pressed state */
266
+ bg-success-subtle /* soft background (e.g. success-50) */
267
+ bg-success-emphasis /* strong/dark variant */
268
+ text-success /* intent-colored text */
269
+ text-success-hover /* hover text */
270
+ text-success-subtle /* subtle intent text */
271
+ border-success /* intent-colored border */
272
+ ```
273
+
274
+ Same pattern for: `primary-*`, `secondary-*`, `neutral-*`, `warning-*`, `danger-*`
275
+
276
+ ### Feedback Tokens (status messages)
277
+
278
+ ```
279
+ bg-feedback-info text-feedback-info /* maps to primary */
280
+ bg-feedback-info-subtle /* soft info background */
281
+ bg-feedback-success text-feedback-success /* maps to success */
282
+ bg-feedback-success-subtle /* soft success background */
283
+ bg-feedback-warning text-feedback-warning /* maps to warning */
284
+ bg-feedback-warning-subtle /* soft warning background */
285
+ bg-feedback-error text-feedback-error /* maps to danger */
286
+ bg-feedback-error-subtle /* soft error background */
287
+ ```
288
+
289
+ ### DON'T invent token names — common mistakes:
290
+
291
+ ```
292
+ /* WRONG — these don't exist */
293
+ text-status-danger bg-status-warning border-l-status-*
294
+ text-feedback-success-fg text-feedback-danger-fg
295
+
296
+ /* CORRECT equivalents */
297
+ text-danger bg-warning border-l-danger
298
+ text-feedback-success text-feedback-error
299
+ ```
300
+
301
+ ### DON'T use primitive colors with dark: overrides:
302
+
303
+ ```
304
+ /* WRONG */
305
+ bg-white dark:bg-neutral-900
306
+
307
+ /* CORRECT */
308
+ bg-surface-base
309
+ ```
310
+
311
+ ### Shadow Tokens
312
+
313
+ ```
314
+ shadow-[var(--blocks-shadow-xs)] /* minimal */
315
+ shadow-[var(--blocks-shadow-sm)] /* buttons */
316
+ shadow-[var(--blocks-shadow-md)] /* hover states */
317
+ shadow-[var(--blocks-shadow-lg)] /* menus, popovers */
318
+ ```
319
+
320
+ ### Z-Index Tokens
321
+
322
+ ```
323
+ z-[var(--z-dropdown)] /* 9999 */
324
+ z-[var(--z-overlay)] /* 1300 */
325
+ z-[var(--z-modal)] /* 1400 */
326
+ z-[var(--z-popover)] /* 1500 */
327
+ z-[var(--z-tooltip)] /* 1800 */
328
+ ```
329
+
330
+ ### Duration Tokens
331
+
332
+ ```
333
+ duration-[var(--blocks-duration-instant)] /* 75ms */
334
+ duration-[var(--blocks-duration-fast)] /* 150ms */
335
+ duration-[var(--blocks-duration-normal)] /* 250ms */
336
+ duration-[var(--blocks-duration-slow)] /* 350ms */
337
+ ```
338
+
339
+ ### Easing Tokens
340
+
341
+ ```
342
+ var(--blocks-ease-confident) /* standard transitions */
343
+ var(--blocks-ease-springy) /* bouncy animations */
344
+ var(--blocks-ease-smooth) /* gentle animations */
345
+ var(--blocks-ease-snappy) /* quick, decisive */
346
+ ```
347
+
348
+ ### Border Radius Scale
349
+
350
+ ```
351
+ rounded-xs /* 0.125rem */ rounded-sm /* 0.25rem */
352
+ rounded-md /* 0.375rem */ rounded-lg /* 0.5rem */
353
+ rounded-xl /* 0.75rem */ rounded-2xl /* 1rem */
354
+ rounded-3xl /* 1.5rem */ rounded-4xl /* 2rem */
355
+ ```
356
+
357
+ ---
358
+
359
+ ## Design Quality
360
+
361
+ These guidelines help you create interfaces with genuine visual identity. They are framed as
362
+ "AVOID → INSTEAD" patterns because knowing what NOT to do is more effective than abstract principles.
363
+
364
+ ### Vary Visual Weight
365
+
366
+ Don't give every element the same visual importance. Vary `variant`, `padding`, and grid span to reflect content hierarchy.
367
+
368
+ ```
369
+ /* AVOID — uniform grid, all cards identical */
370
+ <div class="grid grid-cols-4 gap-4">
371
+ <Card variant="elevated" padding="md">Metric A</Card>
372
+ <Card variant="elevated" padding="md">Metric B</Card>
373
+ <Card variant="elevated" padding="md">Metric C</Card>
374
+ <Card variant="elevated" padding="md">Metric D</Card>
375
+ </div>
376
+
377
+ /* INSTEAD — weight reflects importance */
378
+ <div class="grid grid-cols-4 gap-4">
379
+ <Card variant="elevated" padding="lg" class="col-span-2">Hero metric</Card>
380
+ <Card variant="outlined" padding="md">Secondary</Card>
381
+ <Card variant="outlined" padding="md">Secondary</Card>
382
+ </div>
383
+ ```
384
+
385
+ ### Color = Meaning, Not Decoration
386
+
387
+ Neutral surfaces should dominate (80–90%). Use `intent` and semantic color tokens ONLY for
388
+ their semantic meaning — status, severity, actions — never as visual flair.
389
+
390
+ ```
391
+ /* AVOID — color without purpose */
392
+ <Card class="bg-primary-subtle"> <!-- why primary? -->
393
+ <Badge intent="primary">Active</Badge> <!-- "Active" isn't a primary action -->
394
+
395
+ /* INSTEAD — color communicates state */
396
+ <Card variant="elevated"> <!-- neutral default -->
397
+ <Badge intent="success" variant="soft">Healthy</Badge> <!-- green = healthy -->
398
+ <Badge intent="danger" variant="filled">Critical</Badge> <!-- red = critical -->
399
+ ```
400
+
401
+ ### Spacing Signals Relationships
402
+
403
+ Don't use the same gap everywhere. Tight spacing groups related items; generous spacing
404
+ separates distinct sections.
405
+
406
+ ```
407
+ /* AVOID — uniform spacing */
408
+ <div class="space-y-4">
409
+ <section>...</section> <!-- same gap between sections -->
410
+ <section>...</section>
411
+ </div>
412
+
413
+ /* INSTEAD — spacing hierarchy */
414
+ <div class="space-y-10"> <!-- generous between sections -->
415
+ <section class="space-y-3">...</section> <!-- tight within sections -->
416
+ <section class="space-y-3">...</section>
417
+ </div>
418
+ ```
419
+
420
+ ### Commit to a Shape Language
421
+
422
+ Choose a border-radius philosophy and apply it consistently. Override component defaults
423
+ with `class` or `slotClasses` when your design requires it.
424
+
425
+ | Strategy | Radius | Personality |
426
+ |----------|--------|-------------|
427
+ | Sharp | `rounded-sm` / `rounded` | Technical precision, data-dense |
428
+ | Soft | `rounded-lg` / `rounded-xl` | Professional, approachable |
429
+ | Round | `rounded-2xl` / `rounded-3xl` | Friendly, modern |
430
+
431
+ Use larger radii for hero/prominent elements, smaller radii for compact/data-dense elements.
432
+ Don't rely on component defaults alone — make a deliberate choice:
433
+
434
+ ```svelte
435
+ <!-- Override Card radius for a softer design -->
436
+ <Card class="rounded-2xl" padding="lg">...</Card>
437
+
438
+ <!-- Or set globally via BlocksProvider -->
439
+ <BlocksProvider defaults={{
440
+ Card: { slotClasses: { base: 'rounded-2xl' } }
441
+ }}>
442
+ ```
443
+
444
+ ### Data-Driven Styling
445
+
446
+ Let the data shape the presentation. Different states, severities, or categories should look
447
+ visually distinct — not just carry a different label.
448
+
449
+ ```
450
+ /* AVOID — identical rows regardless of content */
451
+ {#each alerts as alert}
452
+ <div class="py-3 border-b border-border-subtle">
453
+ <Badge>{alert.severity}</Badge> {alert.message}
454
+ </div>
455
+ {/each}
456
+
457
+ /* INSTEAD — severity drives visual weight */
458
+ {#each alerts as alert}
459
+ {@const critical = alert.severity === 'critical'}
460
+ <div class="border-b border-border-subtle {critical ? 'py-4 font-medium' : 'py-2.5'}">
461
+ <Badge intent={severityIntent(alert.severity)}
462
+ variant={critical ? 'filled' : 'soft'}>
463
+ {alert.severity}
464
+ </Badge>
465
+ <span class={critical ? 'text-text-primary' : 'text-text-secondary'}>
466
+ {alert.message}
467
+ </span>
468
+ </div>
469
+ {/each}
470
+ ```
471
+
472
+ ### Don't Copy, Compose
473
+
474
+ Recipes and examples show ONE possible interpretation. Use them for API understanding,
475
+ not as visual templates. Your implementation should:
476
+
477
+ - Vary Card `variant` across the page based on content importance (not all `elevated`)
478
+ - Use different density (padding, gap, text size) for different page sections
479
+ - Make one element per section clearly dominant — if everything is emphasized, nothing is
480
+ - Use `text-text-secondary` and `text-text-tertiary` generously to push supporting content back
481
+ - Choose your OWN spacing rhythm, radius strategy, and color distribution
482
+
483
+ ---
484
+
485
+ ## Customization
486
+
487
+ Four levels of customization, from simple color swaps to fully custom designs.
488
+ **Pick the lowest level that solves your problem** — lower levels preserve more of the
489
+ design system's behavior (dark mode, hover/active cascade, focus rings).
490
+
491
+ ### Level 1: CSS Token Themes
492
+
493
+ Import a theme CSS file after base styles to override the color palette:
494
+ ```css
495
+ @import '@urbicon-ui/blocks/style/index.css';
496
+ @import '@urbicon-ui/blocks/style/themes/ocean.css';
497
+ ```
498
+
499
+ Built-in themes: `ocean.css`, `forest.css`, `sunset.css`, `rose.css`, `neutral.css`.
500
+
501
+ Create custom themes with a `@theme` block:
502
+ ```css
503
+ @theme {
504
+ --color-primary-50: oklch(0.95 0.03 YOUR_HUE);
505
+ --color-primary-500: oklch(0.58 0.13 YOUR_HUE);
506
+ --color-primary-900: oklch(0.26 0.06 YOUR_HUE);
507
+ /* ... full 50–950 scale */
508
+ }
509
+ ```
510
+
511
+ ### Level 2: Presets – Named Project-Defined Styles
512
+
513
+ **Use this when the built-in `intent` palette doesn't fit.** Presets are named, reusable
514
+ `slotClasses` bundles you register once at the app root, then apply per component via
515
+ a `preset="…"` prop. They are the **preferred alternative** to ad-hoc `class="bg-…!"`
516
+ overrides at call sites.
517
+
518
+ Register once at the app root:
519
+ ```svelte
520
+ <script>
521
+ import { BlocksProvider } from '@urbicon-ui/blocks';
522
+ </script>
523
+
524
+ <BlocksProvider
525
+ presets={{
526
+ Button: {
527
+ overlay: {
528
+ slotClasses: {
529
+ base: 'bg-black/20 hover:bg-black/30 active:bg-black/40 text-white border-transparent'
530
+ }
531
+ },
532
+ brand: {
533
+ slotClasses: {
534
+ base: 'bg-[#FF5A1F] hover:bg-[#E04C15] active:bg-[#C53F0D] text-white border-transparent'
535
+ }
536
+ }
537
+ },
538
+ Card: {
539
+ glass: {
540
+ slotClasses: {
541
+ base: 'bg-white/10 backdrop-blur-xl border-white/20'
542
+ }
543
+ }
544
+ }
545
+ }}
546
+ >
547
+ <slot />
548
+ </BlocksProvider>
549
+ ```
550
+
551
+ Then use across the project:
552
+ ```svelte
553
+ <Button preset="overlay">Reinholen</Button>
554
+ <Button preset="brand">Jetzt kaufen</Button>
555
+ <Card preset="glass">Heads-up display</Card>
556
+ ```
557
+
558
+ **Why presets over inline `class` overrides?**
559
+
560
+ ```svelte
561
+ <!-- ❌ AVOID: Defeats hover/active cascade, leaks decisions into every call site,
562
+ requires `!` to out-specify tv() defaults, inconsistent across the project. -->
563
+ <Button intent="primary" class="bg-black/20! hover:bg-black/30! active:bg-black/40!">
564
+ Reinholen
565
+ </Button>
566
+
567
+ <!-- ✅ PREFER: Intent-less, cohesive cascade, reusable across the codebase. -->
568
+ <Button preset="overlay">Reinholen</Button>
569
+ ```
570
+
571
+ When defining a preset, specify **all interactive states explicitly** (`hover:`, `active:`,
572
+ and `focus-visible:` where applicable). The preset's `slotClasses.base` is merged *after*
573
+ the `tv()` defaults, so conflicting Tailwind utilities are resolved by `tailwind-merge`
574
+ (last value wins) — no `!` needed.
575
+
576
+ An unknown preset name emits a dev-only console warning, so typos are discoverable.
577
+
578
+ ### Level 3: BlocksProvider – Global Component Defaults
579
+
580
+ Use `defaults` (distinct from `presets`) to set **project-wide baseline styles** that
581
+ apply to *every* instance of a component — e.g. "all Buttons should be rounded-full in this app":
582
+
583
+ ```svelte
584
+ <BlocksProvider
585
+ defaults={{
586
+ Button: { slotClasses: { base: 'rounded-full font-bold uppercase' } },
587
+ Card: { slotClasses: { base: 'rounded-3xl shadow-2xl' } },
588
+ Input: { slotClasses: { base: 'rounded-full' } }
589
+ }}
590
+ >
591
+ <slot />
592
+ </BlocksProvider>
593
+ ```
594
+
595
+ `defaults` vs. `presets` — when to use which:
596
+ - **`defaults`**: blanket project style applied to every instance (no opt-in required)
597
+ - **`presets`**: named alternative look, opt-in via `preset="…"` prop at the call site
598
+
599
+ Merge priority (lowest → highest):
600
+ 1. `tv()` variant styles (library default)
601
+ 2. `BlocksProvider defaults.slotClasses` (global baseline)
602
+ 3. `BlocksProvider presets[Component][name].slotClasses` (when `preset` prop is set)
603
+ 4. Instance `slotClasses` prop
604
+ 5. Instance `class` prop
605
+
606
+ ### Level 4: Global Unstyled Mode
607
+
608
+ Strip all default styles from every component, then apply your own:
609
+ ```svelte
610
+ <BlocksProvider unstyled defaults={{
611
+ Button: { slotClasses: { base: 'inline-flex items-center border-2 px-6 py-3 font-mono' } }
612
+ }}>
613
+ <slot />
614
+ </BlocksProvider>
615
+ ```
616
+
617
+ ---
618
+
619
+ ## Style Patterns
620
+
621
+ Urbicon UI components are designed to be radically reskinned. Do NOT default to the standard
622
+ look from examples — choose a visual style that fits the project's identity. Every component
623
+ supports `unstyled` (strips all defaults) and `slotClasses` (targeted sub-element overrides).
624
+
625
+ ### Slot Reference
626
+
627
+ | Component | Slots |
628
+ |-----------|-------|
629
+ | Button | `base`, `content`, `spinner` |
630
+ | Card | `base`, `header`, `content`, `footer` |
631
+ | Input | `wrapper`, `container`, `base`, `label`, `message`, `iconContainer` |
632
+ | Badge | `base`, `content`, `removeButton`, `removeIcon` |
633
+ | Dialog | `base`, `overlay`, `header`, `body`, `footer` |
634
+ | Alert | `base`, `icon`, `content`, `title`, `description`, `actions` |
635
+ | Tooltip | `base`, `arrow` |
636
+
637
+ ### Pattern: Glassmorphism
638
+
639
+ ```svelte
640
+ <BlocksProvider defaults={{
641
+ Card: {
642
+ slotClasses: {
643
+ base: 'bg-white/10 backdrop-blur-xl border border-white/20 shadow-2xl rounded-2xl'
644
+ }
645
+ },
646
+ Button: {
647
+ slotClasses: {
648
+ base: 'bg-white/15 backdrop-blur-md border border-white/25 hover:bg-white/25 rounded-xl text-white shadow-lg'
649
+ }
650
+ },
651
+ Input: {
652
+ slotClasses: {
653
+ base: 'bg-white/10 backdrop-blur-sm border-white/20 text-white placeholder:text-white/50 rounded-xl',
654
+ label: 'text-white/80'
655
+ }
656
+ }
657
+ }}>
658
+ ```
659
+
660
+ Combine with a gradient or image background on the page container.
661
+
662
+ ### Pattern: Brutalist / Raw
663
+
664
+ ```svelte
665
+ <BlocksProvider unstyled defaults={{
666
+ Card: {
667
+ slotClasses: {
668
+ base: 'border-4 border-black bg-white p-0 shadow-[8px_8px_0_black]'
669
+ }
670
+ },
671
+ Button: {
672
+ slotClasses: {
673
+ base: 'border-3 border-black bg-yellow-300 px-6 py-3 font-black uppercase tracking-widest hover:bg-black hover:text-yellow-300 active:translate-x-1 active:translate-y-1 active:shadow-none shadow-[4px_4px_0_black]'
674
+ }
675
+ },
676
+ Input: {
677
+ slotClasses: {
678
+ base: 'border-3 border-black bg-white font-mono text-lg',
679
+ label: 'font-black uppercase tracking-widest text-xs'
680
+ }
681
+ },
682
+ Badge: {
683
+ slotClasses: {
684
+ base: 'border-2 border-black bg-lime-400 font-black uppercase text-black rotate-[-2deg]'
685
+ }
686
+ }
687
+ }}>
688
+ ```
689
+
690
+ ### Pattern: Terminal / Hacker
691
+
692
+ ```svelte
693
+ <BlocksProvider unstyled defaults={{
694
+ Card: {
695
+ slotClasses: {
696
+ base: 'border border-green-500/30 bg-black/90 font-mono text-green-400 rounded-none'
697
+ }
698
+ },
699
+ Button: {
700
+ slotClasses: {
701
+ base: 'border border-green-500/50 bg-green-500/10 font-mono text-green-400 rounded-none hover:bg-green-500/20 hover:text-green-300 px-4 py-2 uppercase tracking-wider text-xs'
702
+ }
703
+ },
704
+ Input: {
705
+ slotClasses: {
706
+ base: 'border border-green-500/30 bg-black font-mono text-green-400 rounded-none caret-green-400 placeholder:text-green-800',
707
+ label: 'font-mono text-green-600 uppercase text-[10px] tracking-[0.2em]'
708
+ }
709
+ },
710
+ Badge: {
711
+ slotClasses: {
712
+ base: 'border border-green-500/40 bg-green-500/10 font-mono text-green-400 text-[10px] uppercase rounded-none'
713
+ }
714
+ }
715
+ }}>
716
+ ```
717
+
718
+ ### Pattern: Soft / Organic
719
+
720
+ ```svelte
721
+ <BlocksProvider defaults={{
722
+ Card: {
723
+ slotClasses: {
724
+ base: 'rounded-[2rem] bg-amber-50 border-0 shadow-sm'
725
+ }
726
+ },
727
+ Button: {
728
+ slotClasses: {
729
+ base: 'rounded-full bg-stone-800 text-amber-50 hover:bg-stone-700 px-8 font-light tracking-wide border-0 shadow-none'
730
+ }
731
+ },
732
+ Input: {
733
+ slotClasses: {
734
+ base: 'rounded-2xl border-stone-200 bg-stone-50 font-light',
735
+ label: 'font-light text-stone-500'
736
+ }
737
+ }
738
+ }}>
739
+ ```
740
+
741
+ ### Override Interaction Tokens
742
+
743
+ Change the feel of all components at once:
744
+ ```css
745
+ :root {
746
+ --blocks-ease-confident: cubic-bezier(0.22, 1, 0.36, 1);
747
+ --blocks-duration-normal: 300ms;
748
+ --blocks-scale-hover: 1.00; /* disable scale on hover */
749
+ --blocks-scale-press: 0.98;
750
+ --blocks-focus-ring-width: 3px;
751
+ --blocks-focus-ring-color: oklch(0.7 0.15 var(--primary-hue, 240));
752
+ }
753
+ ```
754
+
755
+ ### Per-Instance Override
756
+
757
+ For one-off customizations without changing the global style:
758
+ ```svelte
759
+ <Card unstyled slotClasses={{ base: 'my-custom-card-class', header: 'my-header' }}>
760
+ ...
761
+ </Card>
762
+
763
+ <Button class="rounded-full px-12 tracking-widest uppercase">
764
+ Custom Button
765
+ </Button>
766
+ ```
767
+
768
+ `class` merges with defaults; `unstyled` + `slotClasses` replaces them entirely.
769
+
770
+ ---
771
+
772
+ ## Callback Naming Convention
773
+
774
+ Native DOM events: lowercase (`onclick`, `onfocus`)
775
+ Custom state callbacks: `on` + PascalCase (`onCheckedChange`, `onValueChange`, `onPageChange`)
776
+
777
+ Always pass the NEW STATE VALUE, not the raw event:
778
+ ```svelte
779
+ <Checkbox onCheckedChange={(checked) => ...} />
780
+ <Menu onValueChange={(value) => ...} />
781
+ <Pagination onPageChange={(page) => ...} />
782
+ ```
783
+
784
+ ---
785
+
786
+ ## i18n Integration
787
+
788
+ ```svelte
789
+ <script>
790
+ import { T, LocaleSwitcher } from '@urbicon-ui/blocks';
791
+ </script>
792
+
793
+ <T key="blocks.button.loading" />
794
+ <LocaleSwitcher />
795
+ ```
796
+
797
+ Supported locales: `en`, `de`. Components auto-translate their internal text.
798
+
799
+ ---
800
+
801
+ ## Common Patterns
802
+
803
+ ### Form with validation
804
+ ```svelte
805
+ <form onsubmit={handleSubmit}>
806
+ <Input label="Email" type="email" bind:value={email} error={errors.email} required />
807
+ <Input label="Password" type="password" bind:value={password} error={errors.password} required />
808
+ <Checkbox label="Remember me" bind:checked={remember} />
809
+ <Button intent="primary" type="submit" loading={submitting}>Sign In</Button>
810
+ </form>
811
+ ```
812
+
813
+ ### Confirmation dialog
814
+ ```svelte
815
+ <Dialog bind:open={showConfirm} title="Delete Item?" intent="danger" size="sm">
816
+ <p>This action cannot be undone.</p>
817
+ {#snippet footer()}
818
+ <Button variant="ghost" onclick={() => showConfirm = false}>Cancel</Button>
819
+ <Button intent="danger" onclick={handleDelete} loading={deleting}>Delete</Button>
820
+ {/snippet}
821
+ </Dialog>
822
+ ```
823
+
824
+ ### Data display card
825
+ ```svelte
826
+ <Card variant="outlined" padding="lg">
827
+ {#snippet header()}
828
+ <div class="flex items-center justify-between">
829
+ <h3 class="text-text-primary font-semibold">Revenue</h3>
830
+ <Badge intent="success" variant="soft">+12%</Badge>
831
+ </div>
832
+ {/snippet}
833
+ <p class="text-3xl font-bold text-text-primary">$48,200</p>
834
+ <p class="text-text-tertiary text-sm">vs. $43,000 last month</p>
835
+ </Card>
836
+ ```
837
+
838
+ ### Navigation tabs
839
+ ```svelte
840
+ <Tab
841
+ tabs={[
842
+ { label: 'Overview', value: 'overview' },
843
+ { label: 'Analytics', value: 'analytics' },
844
+ { label: 'Settings', value: 'settings', disabled: true }
845
+ ]}
846
+ bind:value={activeTab}
847
+ variant="pills"
848
+ />
849
+
850
+ {#if activeTab === 'overview'}
851
+ <OverviewPanel />
852
+ {:else if activeTab === 'analytics'}
853
+ <AnalyticsPanel />
854
+ {/if}
855
+ ```
856
+
857
+ ### Menu with custom items
858
+ ```svelte
859
+ <Menu
860
+ items={users}
861
+ getItemLabel={(u) => u.name}
862
+ getItemValue={(u) => u.id}
863
+ placeholder="Assign to..."
864
+ bind:value={assignee}
865
+ >
866
+ {#snippet customItem(user, isSelected, toggle)}
867
+ <div class="flex items-center gap-2 p-2" onclick={toggle}>
868
+ <Avatar name={user.name} size="xs" />
869
+ <span>{user.name}</span>
870
+ </div>
871
+ {/snippet}
872
+ </Menu>
873
+ ```
874
+
875
+ ### Loading skeleton
876
+ ```svelte
877
+ {#if loading}
878
+ <div class="flex items-center gap-3">
879
+ <Skeleton variant="circular" size="md" />
880
+ <div class="flex-1 flex flex-col gap-2">
881
+ <Skeleton variant="text" size="sm" />
882
+ <Skeleton variant="text" size="xs" class="w-2/3" />
883
+ </div>
884
+ </div>
885
+ {:else}
886
+ <UserCard {user} />
887
+ {/if}
888
+ ```
889
+
890
+ ### Breadcrumb navigation
891
+ ```svelte
892
+ <Breadcrumb items={[
893
+ { label: 'Dashboard', href: '/dashboard' },
894
+ { label: 'Settings', href: '/dashboard/settings' },
895
+ { label: 'Profile' }
896
+ ]} size="sm" />
897
+ ```
898
+
899
+ ---
900
+
901
+ ## Auth Setup
902
+
903
+ `@urbicon-ui/auth` is a zero-runtime-dependency auth system for SvelteKit (JWT sessions,
904
+ refresh-token rotation, passkeys, notifications). Server code imports from
905
+ `@urbicon-ui/auth/server`; client stores and components from `@urbicon-ui/auth`.
906
+ `createAuthHandle` is **mandatory** — it hydrates `locals.user`, guards routes, applies
907
+ the response security headers, and enforces CSRF; the handler factories alone do none of
908
+ that. Setup is staged: start with the dev quickstart, then layer on production hardening.
909
+
910
+ ### Stage 1 — Dev quickstart (no database, no mail server)
911
+
912
+ In-memory adapter + console email transport. State is wiped on restart — **dev only**.
913
+
914
+ ```typescript
915
+ // src/lib/server/auth-setup.ts
916
+ import { createAuthDeps } from '@urbicon-ui/auth/server';
917
+ import { createInMemoryRepos } from '@urbicon-ui/auth/server/adapters/in-memory';
918
+ import { createConsoleEmailTransport } from '@urbicon-ui/auth/server/email/console';
919
+
920
+ export const authDeps = createAuthDeps({
921
+ config: {
922
+ jwt: { secret: 'dev-secret-change-me', cookieSecure: false }, // cookieSecure:false = http dev
923
+ appUrl: 'http://localhost:5173', // required — builds email verification/reset links
924
+ routes: { afterLogin: '/', loginPage: '/auth/login' }
925
+ },
926
+ repos: createInMemoryRepos(),
927
+ email: createConsoleEmailTransport() // dev only — prints emails to the terminal
928
+ });
929
+ ```
930
+
931
+ ```typescript
932
+ // src/hooks.server.ts
933
+ import { createAuthHandle } from '@urbicon-ui/auth/server';
934
+ import { authDeps } from '$lib/server/auth-setup';
935
+ export const handle = createAuthHandle({ config: authDeps.config, repos: authDeps.repos });
936
+ ```
937
+
938
+ **Note — Origin/CSRF is enforced only for requests routed _through_ the handle.** If you later
939
+ add a cross-origin, form-encoded endpoint (an OAuth 2.1 token endpoint, a webhook) _around_
940
+ `createAuthHandle`, SvelteKit's own built-in `csrf.checkOrigin` — which runs in the request
941
+ kernel before any hook, in production builds only — rejects it with `403 "Cross-site POST form
942
+ submissions are forbidden"` (most visibly the OAuth token endpoint: RFC 6749 mandates
943
+ `application/x-www-form-urlencoded` and its callers send no `Origin` header). Set
944
+ `kit.csrf: { trustedOrigins: ['*'] }` in `svelte.config.js` to disable the kernel check and let
945
+ this package's stricter `validateCsrf` be the single Origin gate — safe only when every
946
+ cookie-authenticated mutating route flows through `createAuthHandle` and the bypassed routes are
947
+ cookieless (bearer/PKCE/API-key). JSON endpoints (e.g. MCP JSON-RPC) are unaffected.
948
+
949
+ ```typescript
950
+ // src/routes/api/auth/login/+server.ts — one file per handler
951
+ import { createLoginHandler } from '@urbicon-ui/auth/server';
952
+ import { authDeps } from '$lib/server/auth-setup';
953
+ export const { POST } = createLoginHandler(authDeps);
954
+ // Repeat with createLogoutHandler, createRegisterHandler, createForgotPasswordHandler,
955
+ // createResetPasswordHandler, createVerifyEmailHandler, createMeHandler.
956
+ ```
957
+
958
+ ```svelte
959
+ <!-- src/routes/auth/login/+page.svelte -->
960
+ <script>
961
+ import { LoginPage } from '@urbicon-ui/auth';
962
+ import { en } from '@urbicon-ui/auth/i18n/en';
963
+ import { goto } from '$app/navigation';
964
+ </script>
965
+
966
+ <LoginPage t={en} onSuccess={() => goto('/')} />
967
+ ```
968
+
969
+ `createAuthDeps` applies secure brute-force defaults automatically (login rate-limit
970
+ 5 / 15 min + lockout 5 / 15 min), so even the quickstart isn't an open door.
971
+ `cookieSecure: false` marks the config as dev and suppresses the production hardening
972
+ warnings (and HSTS). Registration is invitation-gated — seed one first:
973
+ `authDeps.repos.invitation.create({ email, role: 'USER', invitedById: 'seed' })`.
974
+
975
+ ### Stage 2 — Production
976
+
977
+ Swap the two dev pieces (in-memory → Prisma, console → a real transport) and enable the
978
+ additive hardening layers. The hook and route stubs are unchanged.
979
+
980
+ ```typescript
981
+ // src/lib/server/auth-setup.ts
982
+ import { createAuthDeps } from '@urbicon-ui/auth/server';
983
+ import { createPrismaRepos } from '@urbicon-ui/auth/server/adapters/prisma';
984
+ import { createLettermintTransport } from '@urbicon-ui/auth/server/email/lettermint';
985
+ import { env } from '$env/dynamic/private';
986
+ import { prisma } from './prisma';
987
+
988
+ type AppRole = 'ADMIN' | 'USER';
989
+
990
+ export const authDeps = createAuthDeps<AppRole>({
991
+ config: {
992
+ jwt: { secret: env.JWT_SECRET }, // cookieSecure defaults true → HTTPS + auto HSTS
993
+ appUrl: env.PUBLIC_APP_URL, // trusted base for email links — never request.url
994
+ csrf: { doubleSubmit: true }, // token layer on top of the always-on Origin check
995
+ refreshToken: { accessTokenTtl: '15m', refreshTokenTtl: '30d' }, // rotating refresh
996
+ rateLimit: { login: { windowMs: 900_000, max: 5 } },
997
+ lockout: { maxAttempts: 5, durationMinutes: 15 },
998
+ routes: { afterLogin: '/', loginPage: '/auth/login' }
999
+ },
1000
+ repos: createPrismaRepos<AppRole>(prisma), // pulls in the refreshToken adapter
1001
+ email: createLettermintTransport({ apiKey: env.LETTERMINT_KEY })
1002
+ });
1003
+ ```
1004
+
1005
+ Add a `refresh` route stub (`createRefreshHandler`) once rotation is on. With
1006
+ `csrf.doubleSubmit` enabled, the bundled stores/components send the CSRF header
1007
+ automatically; custom client fetches use `csrfFetch` from `@urbicon-ui/auth`. Copy the
1008
+ Prisma models from the package's `prisma/auth-schema.prisma`. Before going live: enforce
1009
+ HTTPS, use persistent stores for challenges / refresh tokens / rate limits when running
1010
+ more than one instance, tune `securityHeaders.csp` to your app, and keep `JWT_SECRET` in
1011
+ a secret store with a `keyId` + `previousSecrets` rotation runbook.
1012
+
1013
+ ### Stage 3 — Advanced
1014
+
1015
+ - **Custom persistence adapter** (Drizzle, Kysely, raw SQL): implement the repository interface and prove it upholds the atomic claim/scope contract with `describeRepositoryConformance` from `@urbicon-ui/auth/server/adapters/conformance`.
1016
+ - **JWT key rotation**: set `jwt.keyId` + `jwt.previousSecrets` to roll the signing secret without invalidating live sessions.
1017
+ - **Passkeys (WebAuthn)**: wire `createPasskeyRegistrationOptionsHandler`, `createPasskeyRegistrationVerifyHandler`, `createPasskeyAuthenticationOptionsHandler`, `createPasskeyAuthenticationVerifyHandler` with a `webauthn: WebAuthnConfig` (persistent `challengeStore` at >1 instance; `requireUserVerification: true` to enforce UV), then add `<PasskeyManager>` and `<LoginPage mode="both" passkeyApiPath="/api/auth/passkey">`.
1018
+ - **Notifications + Web Push**: register events with `createNotificationRegistry` server-side; listen with `<NotificationListener>` + `<NotificationCenter>` client-side. `notification.url` is untrusted at navigation time — validate/allow-list it before `goto()`. Mark-read/delete route handlers must scope `userId` from `locals.user`, never the request body (IDOR).
1019
+ - **Federated identity / SSO**: on the roadmap (`createFederatedAuthHandle()`).