@skra/ui 0.2.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 (160) hide show
  1. package/LICENSE +201 -0
  2. package/NOTICE +7 -0
  3. package/README.md +39 -0
  4. package/consumer-guide/README.md +84 -0
  5. package/consumer-guide/accessible-names.md +80 -0
  6. package/consumer-guide/forms-and-binding.md +149 -0
  7. package/consumer-guide/layout.md +86 -0
  8. package/consumer-guide/overlays.md +100 -0
  9. package/consumer-guide/setup-and-gotchas.md +103 -0
  10. package/consumer-guide/theming-and-encapsulation.md +115 -0
  11. package/consumer-guide/wrapping-patterns.md +90 -0
  12. package/fesm2022/skra-ui-accordion.mjs +333 -0
  13. package/fesm2022/skra-ui-accordion.mjs.map +1 -0
  14. package/fesm2022/skra-ui-alert.mjs +110 -0
  15. package/fesm2022/skra-ui-alert.mjs.map +1 -0
  16. package/fesm2022/skra-ui-avatar.mjs +83 -0
  17. package/fesm2022/skra-ui-avatar.mjs.map +1 -0
  18. package/fesm2022/skra-ui-badge.mjs +91 -0
  19. package/fesm2022/skra-ui-badge.mjs.map +1 -0
  20. package/fesm2022/skra-ui-button.mjs +67 -0
  21. package/fesm2022/skra-ui-button.mjs.map +1 -0
  22. package/fesm2022/skra-ui-checkbox.mjs +98 -0
  23. package/fesm2022/skra-ui-checkbox.mjs.map +1 -0
  24. package/fesm2022/skra-ui-chip.mjs +82 -0
  25. package/fesm2022/skra-ui-chip.mjs.map +1 -0
  26. package/fesm2022/skra-ui-combobox.mjs +335 -0
  27. package/fesm2022/skra-ui-combobox.mjs.map +1 -0
  28. package/fesm2022/skra-ui-core.mjs +529 -0
  29. package/fesm2022/skra-ui-core.mjs.map +1 -0
  30. package/fesm2022/skra-ui-counter.mjs +53 -0
  31. package/fesm2022/skra-ui-counter.mjs.map +1 -0
  32. package/fesm2022/skra-ui-datepicker.mjs +867 -0
  33. package/fesm2022/skra-ui-datepicker.mjs.map +1 -0
  34. package/fesm2022/skra-ui-dialog.mjs +133 -0
  35. package/fesm2022/skra-ui-dialog.mjs.map +1 -0
  36. package/fesm2022/skra-ui-field.mjs +293 -0
  37. package/fesm2022/skra-ui-field.mjs.map +1 -0
  38. package/fesm2022/skra-ui-icon.mjs +54 -0
  39. package/fesm2022/skra-ui-icon.mjs.map +1 -0
  40. package/fesm2022/skra-ui-input.mjs +42 -0
  41. package/fesm2022/skra-ui-input.mjs.map +1 -0
  42. package/fesm2022/skra-ui-kbd.mjs +44 -0
  43. package/fesm2022/skra-ui-kbd.mjs.map +1 -0
  44. package/fesm2022/skra-ui-layout.mjs +119 -0
  45. package/fesm2022/skra-ui-layout.mjs.map +1 -0
  46. package/fesm2022/skra-ui-link.mjs +29 -0
  47. package/fesm2022/skra-ui-link.mjs.map +1 -0
  48. package/fesm2022/skra-ui-loading-indicator.mjs +24 -0
  49. package/fesm2022/skra-ui-loading-indicator.mjs.map +1 -0
  50. package/fesm2022/skra-ui-menu.mjs +334 -0
  51. package/fesm2022/skra-ui-menu.mjs.map +1 -0
  52. package/fesm2022/skra-ui-multi-select.mjs +179 -0
  53. package/fesm2022/skra-ui-multi-select.mjs.map +1 -0
  54. package/fesm2022/skra-ui-native-select.mjs +123 -0
  55. package/fesm2022/skra-ui-native-select.mjs.map +1 -0
  56. package/fesm2022/skra-ui-popover.mjs +158 -0
  57. package/fesm2022/skra-ui-popover.mjs.map +1 -0
  58. package/fesm2022/skra-ui-progress.mjs +57 -0
  59. package/fesm2022/skra-ui-progress.mjs.map +1 -0
  60. package/fesm2022/skra-ui-radio.mjs +92 -0
  61. package/fesm2022/skra-ui-radio.mjs.map +1 -0
  62. package/fesm2022/skra-ui-rating-display.mjs +81 -0
  63. package/fesm2022/skra-ui-rating-display.mjs.map +1 -0
  64. package/fesm2022/skra-ui-select.mjs +151 -0
  65. package/fesm2022/skra-ui-select.mjs.map +1 -0
  66. package/fesm2022/skra-ui-skeleton.mjs +65 -0
  67. package/fesm2022/skra-ui-skeleton.mjs.map +1 -0
  68. package/fesm2022/skra-ui-star-rating.mjs +110 -0
  69. package/fesm2022/skra-ui-star-rating.mjs.map +1 -0
  70. package/fesm2022/skra-ui-status-dot.mjs +61 -0
  71. package/fesm2022/skra-ui-status-dot.mjs.map +1 -0
  72. package/fesm2022/skra-ui-switch.mjs +94 -0
  73. package/fesm2022/skra-ui-switch.mjs.map +1 -0
  74. package/fesm2022/skra-ui-tabs.mjs +356 -0
  75. package/fesm2022/skra-ui-tabs.mjs.map +1 -0
  76. package/fesm2022/skra-ui-textarea.mjs +81 -0
  77. package/fesm2022/skra-ui-textarea.mjs.map +1 -0
  78. package/fesm2022/skra-ui-toast.mjs +208 -0
  79. package/fesm2022/skra-ui-toast.mjs.map +1 -0
  80. package/fesm2022/skra-ui-tooltip.mjs +252 -0
  81. package/fesm2022/skra-ui-tooltip.mjs.map +1 -0
  82. package/fesm2022/skra-ui-typography.mjs +35 -0
  83. package/fesm2022/skra-ui-typography.mjs.map +1 -0
  84. package/fesm2022/skra-ui.mjs +4 -0
  85. package/fesm2022/skra-ui.mjs.map +1 -0
  86. package/package.json +175 -0
  87. package/types/skra-ui-accordion.d.ts +196 -0
  88. package/types/skra-ui-accordion.d.ts.map +1 -0
  89. package/types/skra-ui-alert.d.ts +81 -0
  90. package/types/skra-ui-alert.d.ts.map +1 -0
  91. package/types/skra-ui-avatar.d.ts +71 -0
  92. package/types/skra-ui-avatar.d.ts.map +1 -0
  93. package/types/skra-ui-badge.d.ts +73 -0
  94. package/types/skra-ui-badge.d.ts.map +1 -0
  95. package/types/skra-ui-button.d.ts +45 -0
  96. package/types/skra-ui-button.d.ts.map +1 -0
  97. package/types/skra-ui-checkbox.d.ts +52 -0
  98. package/types/skra-ui-checkbox.d.ts.map +1 -0
  99. package/types/skra-ui-chip.d.ts +60 -0
  100. package/types/skra-ui-chip.d.ts.map +1 -0
  101. package/types/skra-ui-combobox.d.ts +95 -0
  102. package/types/skra-ui-combobox.d.ts.map +1 -0
  103. package/types/skra-ui-core.d.ts +314 -0
  104. package/types/skra-ui-core.d.ts.map +1 -0
  105. package/types/skra-ui-counter.d.ts +33 -0
  106. package/types/skra-ui-counter.d.ts.map +1 -0
  107. package/types/skra-ui-datepicker.d.ts +164 -0
  108. package/types/skra-ui-datepicker.d.ts.map +1 -0
  109. package/types/skra-ui-dialog.d.ts +59 -0
  110. package/types/skra-ui-dialog.d.ts.map +1 -0
  111. package/types/skra-ui-field.d.ts +181 -0
  112. package/types/skra-ui-field.d.ts.map +1 -0
  113. package/types/skra-ui-icon.d.ts +38 -0
  114. package/types/skra-ui-icon.d.ts.map +1 -0
  115. package/types/skra-ui-input.d.ts +22 -0
  116. package/types/skra-ui-input.d.ts.map +1 -0
  117. package/types/skra-ui-kbd.d.ts +33 -0
  118. package/types/skra-ui-kbd.d.ts.map +1 -0
  119. package/types/skra-ui-layout.d.ts +86 -0
  120. package/types/skra-ui-layout.d.ts.map +1 -0
  121. package/types/skra-ui-link.d.ts +20 -0
  122. package/types/skra-ui-link.d.ts.map +1 -0
  123. package/types/skra-ui-loading-indicator.d.ts +9 -0
  124. package/types/skra-ui-loading-indicator.d.ts.map +1 -0
  125. package/types/skra-ui-menu.d.ts +116 -0
  126. package/types/skra-ui-menu.d.ts.map +1 -0
  127. package/types/skra-ui-multi-select.d.ts +47 -0
  128. package/types/skra-ui-multi-select.d.ts.map +1 -0
  129. package/types/skra-ui-native-select.d.ts +74 -0
  130. package/types/skra-ui-native-select.d.ts.map +1 -0
  131. package/types/skra-ui-popover.d.ts +57 -0
  132. package/types/skra-ui-popover.d.ts.map +1 -0
  133. package/types/skra-ui-progress.d.ts +41 -0
  134. package/types/skra-ui-progress.d.ts.map +1 -0
  135. package/types/skra-ui-radio.d.ts +54 -0
  136. package/types/skra-ui-radio.d.ts.map +1 -0
  137. package/types/skra-ui-rating-display.d.ts +56 -0
  138. package/types/skra-ui-rating-display.d.ts.map +1 -0
  139. package/types/skra-ui-select.d.ts +43 -0
  140. package/types/skra-ui-select.d.ts.map +1 -0
  141. package/types/skra-ui-skeleton.d.ts +42 -0
  142. package/types/skra-ui-skeleton.d.ts.map +1 -0
  143. package/types/skra-ui-star-rating.d.ts +71 -0
  144. package/types/skra-ui-star-rating.d.ts.map +1 -0
  145. package/types/skra-ui-status-dot.d.ts +62 -0
  146. package/types/skra-ui-status-dot.d.ts.map +1 -0
  147. package/types/skra-ui-switch.d.ts +54 -0
  148. package/types/skra-ui-switch.d.ts.map +1 -0
  149. package/types/skra-ui-tabs.d.ts +191 -0
  150. package/types/skra-ui-tabs.d.ts.map +1 -0
  151. package/types/skra-ui-textarea.d.ts +55 -0
  152. package/types/skra-ui-textarea.d.ts.map +1 -0
  153. package/types/skra-ui-toast.d.ts +140 -0
  154. package/types/skra-ui-toast.d.ts.map +1 -0
  155. package/types/skra-ui-tooltip.d.ts +90 -0
  156. package/types/skra-ui-tooltip.d.ts.map +1 -0
  157. package/types/skra-ui-typography.d.ts +17 -0
  158. package/types/skra-ui-typography.d.ts.map +1 -0
  159. package/types/skra-ui.d.ts +3 -0
  160. package/types/skra-ui.d.ts.map +1 -0
@@ -0,0 +1,100 @@
1
+ # Overlays
2
+
3
+ > Load this for: opening a dialog, tooltip, popover, or menu in the native top layer, and the
4
+ > shadow/iframe limits. Governed by ADR 0044 (native top-layer substrate — no CDK portal, no
5
+ > z-index). For `select` / `combobox` / `datepicker` **value binding**, see `forms-and-binding.md`
6
+ > (they are value-owned overlays).
7
+
8
+ ## Shared model
9
+
10
+ All Skra overlays render in the **browser top layer** via native platform APIs (`<dialog>` +
11
+ the Popover API), not a `document.body` portal. Consequences for you:
12
+
13
+ - **No z-index management.** Stacking is invocation-order; nesting is always correct.
14
+ - **Escape, light-dismiss, `::backdrop`** are platform-owned (per overlay, below).
15
+ - They open correctly **from inside a Shadow DOM root**; per-context limits at the bottom.
16
+
17
+ ## Dialog (modal) — `dialog[skraDialog]`
18
+
19
+ A Component on the native `<dialog>`, opened with the **native** `showModal()` and closed with
20
+ `close()`. `role="dialog"`, `aria-modal`, Escape, the `::backdrop` scrim, scroll-lock and focus
21
+ containment are native:
22
+
23
+ ```html
24
+ <button skraButton (click)="dlg.showModal()">Delete…</button>
25
+
26
+ <dialog skraDialog #dlg aria-labelledby="confirm-title" size="sm">
27
+ <h2 id="confirm-title">Delete item?</h2>
28
+ <p>This can't be undone.</p>
29
+ <button skraButton autofocus (click)="dlg.close()">Cancel</button>
30
+ <button skraButton (click)="confirm(); dlg.close()">Delete</button>
31
+ </dialog>
32
+ ```
33
+
34
+ - **Name it** with `aria-labelledby` → a projected heading (no `ariaLabel` input — ADR 033).
35
+ - **Initial focus** is the consumer-placed native `autofocus` attribute.
36
+ - `size` is `sm | md | lg` (ADR 035).
37
+
38
+ ## Tooltip — `[skraTooltip]` on your own trigger
39
+
40
+ A supplementary, non-interactive **description** shown on hover AND focus. It is **not** the
41
+ accessible name — it merges into the trigger's `aria-describedby`:
42
+
43
+ ```html
44
+ <button skraButton [skraTooltip]="'Open settings'" aria-label="Settings">⚙</button>
45
+ ```
46
+
47
+ An icon-only trigger **still needs its own accessible name** (Path 3 — see
48
+ `accessible-names.md`); the tooltip never substitutes for it.
49
+
50
+ ## Popover (non-modal panel) — `skra-popover`
51
+
52
+ A Component on the panel with projected interactive content. The trigger is **your own button**
53
+ linked by the native `popovertarget`; toggle, light-dismiss, Escape, and stacking are
54
+ platform-owned. **You MUST give the panel an `id`:**
55
+
56
+ ```html
57
+ <button skraButton popovertarget="account-menu">Account</button>
58
+ <skra-popover id="account-menu" aria-labelledby="account-menu-title">
59
+ <h3 id="account-menu-title">Signed in as…</h3>
60
+ <!-- interactive content -->
61
+ </skra-popover>
62
+ ```
63
+
64
+ Focus is **not** trapped/force-moved (non-modal). Name via `aria-labelledby` → projected
65
+ heading.
66
+
67
+ ## Menu (commands) — `skra-menu` + `button[skraMenuItem]`
68
+
69
+ A WAI-ARIA APG menu-button: a homogeneous command set, arrow-key navigated, activating an item
70
+ closes it. Trigger is your own button with `popovertarget` + `aria-haspopup="menu"`; **give the
71
+ panel an `id`**. Focus moves **into** the menu on open:
72
+
73
+ ```html
74
+ <button skraButton popovertarget="row-actions" aria-haspopup="menu">Actions</button>
75
+ <skra-menu id="row-actions">
76
+ <button skraMenuItem (click)="edit()">Edit</button>
77
+ <button skraMenuItem disabled (click)="archive()">Archive</button>
78
+ <button skraMenuItem (click)="remove()">Delete</button>
79
+ </skra-menu>
80
+ ```
81
+
82
+ - **Side effects on open/close:** bind the **native `(toggle)`** event — there is no Skra
83
+ output (native-first).
84
+ - A right-click context menu is **not** a menu trigger mode — use a visible button (SRP +
85
+ WCAG 2.1.1).
86
+
87
+ ## Selection overlays — value binding lives elsewhere
88
+
89
+ `skra-select`, `skra-multi-select`, `skra-combobox`, `skra-datepicker` render their listbox
90
+ in the top layer like the above, **but their value is consumer-owned** — bind with the
91
+ `[value]`/`(valueChange)` bridge documented in `forms-and-binding.md` (class B). The combobox
92
+ filters with consumer-owned data (ADR 0049): you own the filtered option list.
93
+
94
+ ## Limits across boundaries (same as theming — recap)
95
+
96
+ - Overlays **open** from inside a Shadow DOM root (top layer + positioning cross the boundary,
97
+ F-10). But the global-slice pieces — `::backdrop` scrim and `html:has()` scroll-lock — need
98
+ the bridge / don't pierce the boundary (F-6/F-7). See `theming-and-encapsulation.md`.
99
+ - **iframe**: overlays are contained to the frame's own top layer — they cannot overlay the
100
+ parent app (F-11).
@@ -0,0 +1,103 @@
1
+ # Setup & Gotchas
2
+
3
+ > Load this for: first integration, import errors, "which package exports what", and the
4
+ > boolean-attribute / inline-style pitfalls. Core rules live in `README.md` — this is the detail.
5
+
6
+ ## Public entry points (`@skra/ui/*`)
7
+
8
+ Import **only** from these. Never a deep `libs/ui/...` path (not part of the public API;
9
+ will break on any internal refactor). Skra ships as **one package, `@skra/ui`**, with a
10
+ secondary entry point per component (ADR 0054 / O-31 consolidation). The primary `@skra/ui`
11
+ entry exports nothing — always import from a `@skra/ui/<entry>` subpath.
12
+
13
+ | Entry point | Exports (representative) |
14
+ | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
15
+ | `@skra/ui/button` | `button[skraButton]` |
16
+ | `@skra/ui/link` | `a[skraLink]` |
17
+ | `@skra/ui/icon` | icon container atom |
18
+ | `@skra/ui/typography` | `[skraTypography]` |
19
+ | `@skra/ui/layout` | `[skraStack]`, `[skraBox]`, `[skraGrid]` |
20
+ | `@skra/ui/kbd` | `kbd[skraKbd]` |
21
+ | `@skra/ui/field` | `<skra-field>`, `[skraLabel]`, `[skraHint]`, `[skraError]`, `fieldset[skraFieldset]`, `legend[skraLegend]`, `SkraFieldContext` |
22
+ | `@skra/ui/input` | `input[skraInput]` |
23
+ | `@skra/ui/textarea` | `textarea[skraTextarea]`, auto-resize |
24
+ | `@skra/ui/checkbox` | `input[skraCheckbox]` |
25
+ | `@skra/ui/radio` | `input[skraRadio]` |
26
+ | `@skra/ui/switch` | `input[skraSwitch]` |
27
+ | `@skra/ui/counter` | `skra-counter` |
28
+ | `@skra/ui/star-rating` | `skra-star-rating` |
29
+ | `@skra/ui/native-select` | `select[skraSelect]` (`SkraNativeSelect`) |
30
+ | `@skra/ui/dialog` | `dialog[skraDialog]` |
31
+ | `@skra/ui/tooltip` | `[skraTooltip]` |
32
+ | `@skra/ui/popover` | `skra-popover` |
33
+ | `@skra/ui/menu` | `skra-menu`, `skra-menu-item` |
34
+ | `@skra/ui/select` | `skra-select` (+ `SkraOption` & option directives, re-exported) |
35
+ | `@skra/ui/multi-select` | `skra-multi-select` (+ `SkraOption` & option directives, re-exported) |
36
+ | `@skra/ui/combobox` | `skra-combobox` (+ `SkraOption` & option directives, re-exported) |
37
+ | `@skra/ui/datepicker` | `skra-datepicker` |
38
+ | `@skra/ui/alert` | `skra-alert` |
39
+ | `@skra/ui/badge` | `skra-badge` |
40
+ | `@skra/ui/chip` | `skra-chip` |
41
+ | `@skra/ui/toast` | `skra-toast`, `skra-toast-region` |
42
+ | `@skra/ui/tabs` | `[skraTabs]`, `[skraTablist]`, `button[skraTab]`, `[skraTabPanel]` |
43
+ | `@skra/ui/accordion` | `[skraAccordion]` family |
44
+ | `@skra/ui/progress` | `progress[skraProgress]` |
45
+ | `@skra/ui/skeleton` | `skra-skeleton` |
46
+ | `@skra/ui/loading-indicator` | `[skraLoadingIndicator]` |
47
+ | `@skra/ui/avatar` | `skra-avatar` |
48
+ | `@skra/ui/status-dot` | `skra-status-dot` |
49
+ | `@skra/ui/rating-display` | `skra-rating-display` |
50
+
51
+ > `@skra/ui/core` exists for cross-component shared satellites (`SkraOption` & option
52
+ > directives, `SkraSelectTrigger`, `SKRA_LISTBOX_PANEL`). Prefer the re-exports from
53
+ > `select` / `multi-select` / `combobox`; you rarely import `core` directly.
54
+ >
55
+ > `@skra/shadow-dom-bridge` is a delivery utility, not a component — see
56
+ > `theming-and-encapsulation.md`.
57
+
58
+ ## Gotcha 1 — boolean attributes: write the bare form
59
+
60
+ Skra boolean inputs carry `{ transform: booleanAttribute }` (lint-enforced across `libs/ui`
61
+ by `boolean-input-validator.mjs`), so the **native bare attribute works** — write it the
62
+ HTML-idiomatic way:
63
+
64
+ ```html
65
+ <skra-toast dismissible>…</skra-toast>
66
+ <!-- ✓ -->
67
+ <div skraStack wrap>…</div>
68
+ <!-- ✓ -->
69
+ ```
70
+
71
+ `[dismissible]="true"` also compiles but is unnecessary. (Historically the transform was
72
+ missing and the bare form broke under `strictTemplates` — F-18; closed library-wide in
73
+ session 10, so the workaround is no longer needed.)
74
+
75
+ ## Gotcha 2 — never style with inline `style` or `[style.--var]`
76
+
77
+ Skra reflects state as `data-skra-*` attributes and maps them to tokens in CSS (ADR 021,
78
+ CSP-safe). To override a token for a subtree, scope it with a **CSS class or a data-attribute
79
+ on an ancestor**, never inline:
80
+
81
+ ```html
82
+ <!-- ✗ CSP-violating, and not how the token cascade is meant to work -->
83
+ <skra-badge style="--skra-comp-badge-background: red">…</skra-badge>
84
+
85
+ <!-- ✓ scope on an ancestor, in a stylesheet -->
86
+ <div class="danger-zone"><skra-badge>…</skra-badge></div>
87
+ ```
88
+
89
+ ```css
90
+ .danger-zone {
91
+ --skra-comp-badge-background: var(--skra-sys-color-danger-base);
92
+ }
93
+ ```
94
+
95
+ ## Gotcha 3 — components carry `margin: 0` (ADR 022)
96
+
97
+ Spacing between components is **never** a margin on the component. Use a layout atom
98
+ (`[skraStack]`/`[skraBox]`/`[skraGrid]`) — see `layout.md`.
99
+
100
+ ## Gotcha 4 — don't reach for relative imports as an escape hatch
101
+
102
+ If something you need isn't on a public `@skra/ui/*` entry point, that is a finding (raise it),
103
+ not a reason to import from `libs/`. The public surface is the contract.
@@ -0,0 +1,115 @@
1
+ # Theming & Encapsulation
2
+
3
+ > Load this for: loading the token layers, switching skin / dark mode / density at runtime,
4
+ > and mounting Skra under Shadow DOM / a microfrontend / an iframe. Governed by ADR 002
5
+ > (token cascade), ADR 042 §3.4 (value/structure split), ADR 012 §5 (delivery), ADR 036 §3.7
6
+ > (baseline hook), ADR 0047 (runtime-scoped skins).
7
+
8
+ ## Mental model — values inherit, structure does not
9
+
10
+ Skra styling has two layers, and they behave differently across DOM boundaries:
11
+
12
+ - **Token VALUES** (`--skra-*` custom properties) — set by the theme/comp/skin artifacts.
13
+ They **inherit** through the DOM, **including across a Shadow DOM boundary**. This is what
14
+ lets a runtime skin swap re-paint a shadow subtree with zero component changes (F-9).
15
+ - **STRUCTURE** (selector-bearing rules: `:host`, `data-skra-*` state, slices, the variant
16
+ matrix) — injected as document-scoped `<style>` (Emulated, ADR 001). These do **not** cross
17
+ into a shadow root; inside one you need the bridge (below).
18
+
19
+ ## Loading the token layers (global stylesheet)
20
+
21
+ A consumer imports the built token artifacts once, globally. **Order matters** (F-14): base →
22
+ comp → dark → slices → scoped skins → overlay presets (overlays last so they keep overriding
23
+ a skin at equal specificity):
24
+
25
+ ```scss
26
+ /* paths shown as in the repo; an external consumer imports the equivalent published artifacts */
27
+ @import 'skra/_theme.scss'; // ref + sys token values (:root)
28
+ @import 'skra/_comp.scss'; // component token defaults
29
+ @import 'skra/_theme.dark.scss'; // dark-scheme value block
30
+ @import 'skra/_baseline.scss'; // typography/canvas baseline (needs the hook — below)
31
+ @import 'skra/_form-controls.scss'; // native control skin slice
32
+ @import 'skra/_overlay.scss'; // ::backdrop scrim + modal scroll-lock slice
33
+ @import 'skra/utilities.scss'; // skra-sr-only etc.
34
+ @import 'skra/_density.compact.scss';
35
+ @import 'skra/_density.comfortable.scss';
36
+
37
+ /* additional skins ship as runtime-scoped artifacts (ADR 0047), keyed to [data-skra-skin] */
38
+ @import 'brutal/_theme.scoped.scss';
39
+ @import 'brutal/_theme.dark.scoped.scss';
40
+ ```
41
+
42
+ > Exact import specifiers depend on how the tokens are distributed to your project (npm
43
+ > package vs vendored `dist/`). The layer set and order are the contract; the paths are not.
44
+
45
+ ## Switching skin / scheme / density at runtime
46
+
47
+ These are **consumer-scope attributes** on an ancestor element — NOT component inputs and NOT
48
+ state reflection. Any subtree carrying them re-resolves its token values:
49
+
50
+ | Attribute | Values | Default (omit the attribute) |
51
+ | ------------------------- | -------------------------------------------------- | ---------------------------- |
52
+ | `data-skra-skin` | `brutal`, … (whatever scoped artifacts you loaded) | the base skin |
53
+ | `data-skra-color-scheme` | `dark` | light |
54
+ | `data-skra-density` | `compact`, `comfortable` | default density |
55
+
56
+ ```html
57
+ <div [attr.data-skra-skin]="skin()" [attr.data-skra-color-scheme]="scheme() === 'dark' ? 'dark' : null" [attr.data-skra-density]="density() === 'default' ? null : density()">
58
+ <!-- Skra subtree re-paints when any of these change -->
59
+ </div>
60
+ ```
61
+
62
+ Bind `null` for the default so you don't emit `data-skra-color-scheme="light"` etc. — the
63
+ base values live at `:root`, the attribute only _overrides_.
64
+
65
+ ### The baseline hook (`data-skra-baseline`)
66
+
67
+ `_baseline.scss` (document typography + canvas background) is **inert until you add the
68
+ `data-skra-baseline` attribute** to the scope that should own it (ADR 036 §3.7). It is
69
+ **static** — always present, not toggled like the design-context attributes above:
70
+
71
+ - `<html data-skra-baseline>` — Skra owns the page (canvas bg reaches the viewport, sets the
72
+ rem root). Use when Skra is your whole app.
73
+ - `<div data-skra-baseline>` — guest/microfrontend: scopes typography + canvas to the subtree
74
+ only, leaving the host shell's `<html>` untouched.
75
+
76
+ ## Mounting Skra inside a Shadow DOM root (microfrontend isolation)
77
+
78
+ Inside a shadow root, token **values still inherit** (skins work), but the document-scoped
79
+ STRUCTURE does not reach in. Adopt it with the bridge:
80
+
81
+ ```ts
82
+ import { registerSkraStyles } from '@skra/shadow-dom-bridge';
83
+
84
+ // after the Skra content inside the shadow root has rendered at least once
85
+ registerSkraStyles(shadowRoot);
86
+ // rebuild the cached aggregate if more Skra styles were injected since:
87
+ registerSkraStyles(shadowRoot, { refresh: true });
88
+ ```
89
+
90
+ `registerSkraStyles(shadowRoot, options?)` adopts Skra's **structural** stylesheets
91
+ (per-component Emulated rules incl. the variant matrix, `_baseline`, `_form-controls`,
92
+ `utilities`) into the root. It deliberately **skips the token value layers** — those inherit,
93
+ and re-declaring them inside would freeze shell-driven skin morph (ADR 042 §3.4). Options:
94
+ `marker` (default `/skra/i`), `fallback` (`<style>` injection when constructable stylesheets
95
+ are unavailable, default `true`), `refresh`. There is also `unregisterSkraStyles(shadowRoot)`.
96
+
97
+ **This applies to Angular `ViewEncapsulation.ShadowDom` too**, not only foreign `attachShadow()`
98
+ roots: Angular replicates _component_ styles into a ShadowDom root but NOT the global slices
99
+ (F-12) — so without the bridge, `skra-sr-only` content renders visible (e.g. the datepicker's
100
+ per-day SR labels overflow the grid — F-15), `::backdrop`/scroll-lock are missing (F-7), etc.
101
+
102
+ ## Limits that do NOT cross a boundary (by design)
103
+
104
+ The native top-layer substrate (ADR 0044) has per-context limits — these are architecture, not
105
+ bugs:
106
+
107
+ - **Modal scroll-lock** uses `html:has(dialog[skraDialog]:modal)`, and `:has()` does not pierce
108
+ a shadow boundary — a dialog opened from inside a shadow root does **not** lock the outer page
109
+ scroll (F-6). For shift-free locking when Skra owns the page, set
110
+ `html { scrollbar-gutter: stable }` globally.
111
+ - **`::backdrop` scrim** lives in the `_overlay.scss` global slice; inside a shadow root it
112
+ needs the bridge (F-7).
113
+ - **iframe** — overlays open in the **iframe's own top layer, contained to the frame** (F-11);
114
+ they cannot overlay the parent app. Pass design context into the frame via query params /
115
+ reload. This is a fundamental limit of the per-document top layer, not a defect.
@@ -0,0 +1,90 @@
1
+ # Wrapping Skra in Your Own Components
2
+
3
+ > Load this for: building project-specific components on top of Skra atoms to simplify the
4
+ > API down to the inputs your app actually needs. Governed by the *atoms-agnostic* doctrine +
5
+ > ADR 037 (consumer owns value/ergonomics).
6
+
7
+ ## Why wrap, and where the wrapper belongs
8
+
9
+ Skra atoms are deliberately low-level and semantic-only — they ship a11y + tokens + binding
10
+ seams, not app ergonomics. The intended consumption shape is: **your app wraps Skra atoms in
11
+ higher-level components** that expose just the inputs your domain needs.
12
+
13
+ The hard rule: **the wrapper lives in YOUR app (or a consumer library), never back inside
14
+ `@skra/*`.** Pulling app ergonomics into the atom breaks the agnostic contract (ADR 037). If a
15
+ wrapper becomes reusable across several of your apps, promote it to **your own library built ON
16
+ Skra** (`@yourorg/ui`), not into Skra core.
17
+
18
+ ## Pattern 1 — a field wrapper (collapse the field boilerplate)
19
+
20
+ The `<skra-field>` + label + control + hint + error + binding cluster is verbose to repeat.
21
+ Wrap it once:
22
+
23
+ ```ts
24
+ // app-text-field.component.ts (YOUR code)
25
+ @Component({
26
+ selector: 'app-text-field',
27
+ standalone: true,
28
+ imports: [SkraField, SkraInput, SkraLabelFor, SkraHint, SkraError],
29
+ template: `
30
+ <skra-field>
31
+ <label skraLabelFor>{{ label() }}</label>
32
+ <input skraInput [formField]="field()" />
33
+ @if (hint()) { <skra-hint>{{ hint() }}</skra-hint> }
34
+ @if (errorText()) { <skra-error>{{ errorText() }}</skra-error> }
35
+ </skra-field>
36
+ `,
37
+ })
38
+ export class AppTextField {
39
+ readonly label = input.required<string>();
40
+ readonly field = input.required<Field<string>>(); // your Signal Forms node type
41
+ readonly hint = input<string>();
42
+ readonly errorText = input<string>();
43
+ }
44
+ ```
45
+
46
+ ```html
47
+ <!-- call site: domain-minimal API -->
48
+ <app-text-field label="Email" [field]="form().email" hint="We never share it." />
49
+ ```
50
+
51
+ **What NOT to break when wrapping:**
52
+
53
+ - Don't re-implement a11y — keep the control inside `<skra-field>` so the DI context still
54
+ wires `id`/`for`/`aria-describedby` (ADR 013). Don't add `aria-label` (ADR 033).
55
+ - Don't introduce inline styles to theme — scope tokens with a class (ADR 021).
56
+ - Keep the accessible-name path intact (the projected `<label skraLabelFor>` IS the name).
57
+
58
+ ## Pattern 2 — a value-bridge adapter for class-B overlays
59
+
60
+ The class-B overlays (`skra-select`, `skra-datepicker`, `skra-star-rating`, …) need the
61
+ two-binding `[value]`/`(valueChange)` bridge (see `forms-and-binding.md`). If that recurs, the
62
+ sanctioned shape is a **consumer-side adapter directive** that maps a form node ↔
63
+ value/valueChange, so the call site goes back to one binding:
64
+
65
+ ```ts
66
+ // app-control.directive.ts (YOUR interop layer — a sketch)
67
+ @Directive({ selector: '[appControl]', standalone: true })
68
+ export class AppControl<T> {
69
+ readonly appControl = input.required<Field<T>>();
70
+ // read [value] from the host element + write back on (valueChange),
71
+ // syncing this.appControl().value() ↔ the field node
72
+ }
73
+ ```
74
+
75
+ ```html
76
+ <skra-select [appControl]="form().country">…</skra-select>
77
+ ```
78
+
79
+ This adapter is **yours**, in your interop layer — it must not live in the atom (ADR 037). The
80
+ friction journal calls this candidate `[skraControl]` (F-3).
81
+
82
+ ## The open question — `@skra/angular-interop`? (resolved: not now)
83
+
84
+ If *every* Angular consumer needs the same value-bridge adapter, there is an argument that
85
+ Skra could ship an **optional, opt-in** `@skra/angular-interop` package — separate from the
86
+ agnostic atoms (atoms stay pure; interop is a voluntary layer on top). **Resolved 2026-06-25
87
+ (ADR 037 §7): DECLINED for now** — with one consumer and a still-iterating Signal Forms API,
88
+ the bridge stays consumer-owned (Pattern 2). The trigger to revisit, then via its own ADR:
89
+ several real consumers feeling the friction AND a stabilised Angular forms API (that ADR must
90
+ first resolve strategy-agnosticism — Signal Forms AND Reactive via a seam, never bless one).