@skra/ui 0.2.0 → 0.3.1

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 (90) hide show
  1. package/LICENSE +201 -201
  2. package/NOTICE +7 -7
  3. package/README.md +39 -39
  4. package/consumer-guide/README.md +84 -84
  5. package/consumer-guide/forms-and-binding.md +19 -3
  6. package/consumer-guide/layout.md +19 -0
  7. package/consumer-guide/overlays.md +100 -100
  8. package/consumer-guide/setup-and-gotchas.md +165 -103
  9. package/consumer-guide/theming-and-encapsulation.md +129 -115
  10. package/fesm2022/skra-ui-accordion.mjs +61 -43
  11. package/fesm2022/skra-ui-accordion.mjs.map +1 -1
  12. package/fesm2022/skra-ui-alert.mjs +26 -18
  13. package/fesm2022/skra-ui-alert.mjs.map +1 -1
  14. package/fesm2022/skra-ui-avatar.mjs +12 -8
  15. package/fesm2022/skra-ui-avatar.mjs.map +1 -1
  16. package/fesm2022/skra-ui-badge.mjs +19 -13
  17. package/fesm2022/skra-ui-badge.mjs.map +1 -1
  18. package/fesm2022/skra-ui-button.mjs +28 -19
  19. package/fesm2022/skra-ui-button.mjs.map +1 -1
  20. package/fesm2022/skra-ui-checkbox.mjs +3 -3
  21. package/fesm2022/skra-ui-checkbox.mjs.map +1 -1
  22. package/fesm2022/skra-ui-chip.mjs +15 -11
  23. package/fesm2022/skra-ui-chip.mjs.map +1 -1
  24. package/fesm2022/skra-ui-combobox.mjs +135 -130
  25. package/fesm2022/skra-ui-combobox.mjs.map +1 -1
  26. package/fesm2022/skra-ui-core.mjs +95 -73
  27. package/fesm2022/skra-ui-core.mjs.map +1 -1
  28. package/fesm2022/skra-ui-counter.mjs +9 -6
  29. package/fesm2022/skra-ui-counter.mjs.map +1 -1
  30. package/fesm2022/skra-ui-datepicker.mjs +334 -307
  31. package/fesm2022/skra-ui-datepicker.mjs.map +1 -1
  32. package/fesm2022/skra-ui-dialog.mjs +5 -4
  33. package/fesm2022/skra-ui-dialog.mjs.map +1 -1
  34. package/fesm2022/skra-ui-field.mjs +65 -43
  35. package/fesm2022/skra-ui-field.mjs.map +1 -1
  36. package/fesm2022/skra-ui-icon.mjs +5 -4
  37. package/fesm2022/skra-ui-icon.mjs.map +1 -1
  38. package/fesm2022/skra-ui-input.mjs +53 -12
  39. package/fesm2022/skra-ui-input.mjs.map +1 -1
  40. package/fesm2022/skra-ui-kbd.mjs +3 -3
  41. package/fesm2022/skra-ui-kbd.mjs.map +1 -1
  42. package/fesm2022/skra-ui-layout.mjs +33 -21
  43. package/fesm2022/skra-ui-layout.mjs.map +1 -1
  44. package/fesm2022/skra-ui-link.mjs +3 -3
  45. package/fesm2022/skra-ui-link.mjs.map +1 -1
  46. package/fesm2022/skra-ui-loading-indicator.mjs +3 -3
  47. package/fesm2022/skra-ui-loading-indicator.mjs.map +1 -1
  48. package/fesm2022/skra-ui-menu.mjs +8 -7
  49. package/fesm2022/skra-ui-menu.mjs.map +1 -1
  50. package/fesm2022/skra-ui-multi-select.mjs +102 -98
  51. package/fesm2022/skra-ui-multi-select.mjs.map +1 -1
  52. package/fesm2022/skra-ui-native-select.mjs +35 -17
  53. package/fesm2022/skra-ui-native-select.mjs.map +1 -1
  54. package/fesm2022/skra-ui-popover.mjs +5 -4
  55. package/fesm2022/skra-ui-popover.mjs.map +1 -1
  56. package/fesm2022/skra-ui-progress.mjs +7 -5
  57. package/fesm2022/skra-ui-progress.mjs.map +1 -1
  58. package/fesm2022/skra-ui-radio.mjs +3 -3
  59. package/fesm2022/skra-ui-radio.mjs.map +1 -1
  60. package/fesm2022/skra-ui-rating-display.mjs +14 -9
  61. package/fesm2022/skra-ui-rating-display.mjs.map +1 -1
  62. package/fesm2022/skra-ui-select.mjs +98 -95
  63. package/fesm2022/skra-ui-select.mjs.map +1 -1
  64. package/fesm2022/skra-ui-skeleton.mjs +16 -13
  65. package/fesm2022/skra-ui-skeleton.mjs.map +1 -1
  66. package/fesm2022/skra-ui-star-rating.mjs +18 -11
  67. package/fesm2022/skra-ui-star-rating.mjs.map +1 -1
  68. package/fesm2022/skra-ui-status-dot.mjs +7 -5
  69. package/fesm2022/skra-ui-status-dot.mjs.map +1 -1
  70. package/fesm2022/skra-ui-switch.mjs +3 -3
  71. package/fesm2022/skra-ui-switch.mjs.map +1 -1
  72. package/fesm2022/skra-ui-tabs.mjs +43 -29
  73. package/fesm2022/skra-ui-tabs.mjs.map +1 -1
  74. package/fesm2022/skra-ui-textarea.mjs +41 -15
  75. package/fesm2022/skra-ui-textarea.mjs.map +1 -1
  76. package/fesm2022/skra-ui-toast.mjs +35 -23
  77. package/fesm2022/skra-ui-toast.mjs.map +1 -1
  78. package/fesm2022/skra-ui-tooltip.mjs +12 -9
  79. package/fesm2022/skra-ui-tooltip.mjs.map +1 -1
  80. package/fesm2022/skra-ui-typography.mjs +9 -6
  81. package/fesm2022/skra-ui-typography.mjs.map +1 -1
  82. package/package.json +6 -6
  83. package/types/skra-ui-field.d.ts +3 -3
  84. package/types/skra-ui-field.d.ts.map +1 -1
  85. package/types/skra-ui-input.d.ts +24 -1
  86. package/types/skra-ui-input.d.ts.map +1 -1
  87. package/types/skra-ui-native-select.d.ts +9 -1
  88. package/types/skra-ui-native-select.d.ts.map +1 -1
  89. package/types/skra-ui-textarea.d.ts +10 -1
  90. package/types/skra-ui-textarea.d.ts.map +1 -1
@@ -1,103 +1,165 @@
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.
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 — `skraTypography` takes its role **on the selector**, not a `role` input
101
+
102
+ `[skraTypography]` is the one atom whose main input is aliased to its own selector. The
103
+ property is called `role`, but you never write `role="…"` — that would collide with the
104
+ native ARIA `role` attribute, which is exactly why it is aliased:
105
+
106
+ ```html
107
+ <!-- ✓ the role goes on the selector attribute -->
108
+ <h1 skraTypography="headline-medium">Materials</h1>
109
+ <p skraTypography="body-small">Everything uploaded this month.</p>
110
+
111
+ <!-- ✓ bare = the default role, `body-medium` -->
112
+ <span skraTypography>Plain body text</span>
113
+
114
+ <!-- ✗ `role` is the ARIA attribute here; the type error is TS2322 on an unknown input -->
115
+ <h1 skraTypography role="headline-medium">Materials</h1>
116
+ ```
117
+
118
+ Roles (ADR 036): `display-{large,medium,small}` · `headline-*` · `title-*` · `body-*` ·
119
+ `label-*`. The other inputs are plain and unaliased:
120
+
121
+ ```html
122
+ <p skraTypography="body-small" severity="error">Upload failed.</p>
123
+ <p skraTypography="body-medium" truncate="multi" [lineClamp]="3">Long description…</p>
124
+ ```
125
+
126
+ `skraTypography` is a **component on a native host** — put it on the element that already
127
+ carries the right semantics (`<h1>`, `<p>`, `<span>`). It sets no heading level and no role
128
+ of its own; the visual scale and the document outline stay independent.
129
+
130
+ ## Gotcha 5 — form atoms work standalone; `<skra-field>` is what adds label/hint/error wiring
131
+
132
+ `input[skraInput]`, `textarea[skraTextarea]` and `select[skraSelect]` inject
133
+ `SkraFieldContext` **optionally** (ADR 013 §3.7), exactly like `skraCheckbox` / `skraRadio` /
134
+ `skraSwitch`. Outside a field they render the skin and touch nothing else — your `id`,
135
+ `disabled` and `readonly` are left alone:
136
+
137
+ ```html
138
+ <!-- ✓ toolbar search: native implicit label, no field ceremony -->
139
+ <label>Search <input skraInput type="search" placeholder="Filter by name…" /></label>
140
+
141
+ <!-- ✓ genuinely label-less control: aria-label on the native host (ADR 033 Path 3) -->
142
+ <input skraInput type="search" aria-label="Search materials" />
143
+ ```
144
+
145
+ Reach for `<skra-field>` when you need what the field actually provides — a visible label
146
+ associated by DI, a hint, an error, and the `required`/`disabled`/`readonly`/`hasError`
147
+ cascade across all of them:
148
+
149
+ ```html
150
+ <skra-field [hasError]="!email().valid">
151
+ <label skraLabelFor>Email</label>
152
+ <input skraInput type="email" />
153
+ <skra-hint>We only use this for receipts.</skra-hint>
154
+ <skra-error>Enter a valid address.</skra-error>
155
+ </skra-field>
156
+ ```
157
+
158
+ > The field does **not** guarantee an accessible name — `<skra-field><input skraInput /></skra-field>`
159
+ > with no `label[skraLabelFor]` renders an unnamed control. Naming is on you either way; run
160
+ > axe over your screens.
161
+
162
+ ## Gotcha 6 — don't reach for relative imports as an escape hatch
163
+
164
+ If something you need isn't on a public `@skra/ui/*` entry point, that is a finding (raise it),
165
+ not a reason to import from `libs/`. The public surface is the contract.
@@ -1,115 +1,129 @@
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.
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
+ @use 'pkg:@skra/tokens/skra/_theme.scss' as *; // ref + sys token values (:root)
27
+ @use 'pkg:@skra/tokens/skra/_comp.scss' as *; // component token defaults
28
+ @use 'pkg:@skra/tokens/skra/_theme.dark.scss' as *; // dark-scheme value block
29
+ @use 'pkg:@skra/tokens/skra/_baseline.scss' as *; // typography/canvas baseline (needs the hook — below)
30
+ @use 'pkg:@skra/tokens/skra/_form-controls.scss' as *; // native control skin slice
31
+ @use 'pkg:@skra/tokens/skra/_overlay.scss' as *; // ::backdrop scrim + modal scroll-lock slice
32
+ @use 'pkg:@skra/tokens/skra/utilities.scss' as *; // skra-sr-only etc.
33
+ @use 'pkg:@skra/tokens/skra/_density.compact.scss' as *;
34
+ @use 'pkg:@skra/tokens/skra/_density.comfortable.scss' as *;
35
+
36
+ /* additional skins ship as runtime-scoped artifacts (ADR 0047), keyed to [data-skra-skin] */
37
+ @use 'pkg:@skra/tokens/brutal/_theme.scoped.scss' as *;
38
+ @use 'pkg:@skra/tokens/brutal/_theme.dark.scoped.scss' as *;
39
+ ```
40
+
41
+ **`as *` is mandatory, and it is the single most common first-build failure.** `@use`
42
+ derives a namespace from the file name, so `_theme.scss` and `_theme.dark.scss` both claim
43
+ `theme` and Sass aborts with _"There's already a module with namespace 'theme'"_ — likewise
44
+ for the `_density.compact` / `_density.comfortable` pair and every `.scoped` pair. The layers
45
+ declare CSS custom properties and no Sass members, so there is nothing for a namespace to
46
+ disambiguate and `as *` costs nothing.
47
+
48
+ **`pkg:` needs no build config.** It uses the Sass package importer, which `@angular/build`
49
+ enables by default. If your toolchain does not, drop the prefix and add `node_modules` to the
50
+ load path — `"stylePreprocessorOptions": { "includePaths": ["node_modules"] }` in the build
51
+ target, then `@use '@skra/tokens/skra/_theme.scss' as *;`. Without one of the two, a bare
52
+ `@skra/tokens/...` specifier does not resolve at all.
53
+
54
+ **Do not use `@import`** — deprecated in Dart Sass and slated for removal.
55
+
56
+ > Exact specifiers depend on how the tokens reach your project (npm package vs vendored
57
+ > `dist/`). The layer set and order are the contract; the paths are not.
58
+
59
+ ## Switching skin / scheme / density at runtime
60
+
61
+ These are **consumer-scope attributes** on an ancestor element — NOT component inputs and NOT
62
+ state reflection. Any subtree carrying them re-resolves its token values:
63
+
64
+ | Attribute | Values | Default (omit the attribute) |
65
+ | ------------------------- | -------------------------------------------------- | ---------------------------- |
66
+ | `data-skra-skin` | `brutal`, … (whatever scoped artifacts you loaded) | the base skin |
67
+ | `data-skra-color-scheme` | `dark` | light |
68
+ | `data-skra-density` | `compact`, `comfortable` | default density |
69
+
70
+ ```html
71
+ <div [attr.data-skra-skin]="skin()" [attr.data-skra-color-scheme]="scheme() === 'dark' ? 'dark' : null" [attr.data-skra-density]="density() === 'default' ? null : density()">
72
+ <!-- Skra subtree re-paints when any of these change -->
73
+ </div>
74
+ ```
75
+
76
+ Bind `null` for the default so you don't emit `data-skra-color-scheme="light"` etc. — the
77
+ base values live at `:root`, the attribute only _overrides_.
78
+
79
+ ### The baseline hook (`data-skra-baseline`)
80
+
81
+ `_baseline.scss` (document typography + canvas background) is **inert until you add the
82
+ `data-skra-baseline` attribute** to the scope that should own it (ADR 036 §3.7). It is
83
+ **static** — always present, not toggled like the design-context attributes above:
84
+
85
+ - `<html data-skra-baseline>` — Skra owns the page (canvas bg reaches the viewport, sets the
86
+ rem root). Use when Skra is your whole app.
87
+ - `<div data-skra-baseline>` — guest/microfrontend: scopes typography + canvas to the subtree
88
+ only, leaving the host shell's `<html>` untouched.
89
+
90
+ ## Mounting Skra inside a Shadow DOM root (microfrontend isolation)
91
+
92
+ Inside a shadow root, token **values still inherit** (skins work), but the document-scoped
93
+ STRUCTURE does not reach in. Adopt it with the bridge:
94
+
95
+ ```ts
96
+ import { registerSkraStyles } from '@skra/shadow-dom-bridge';
97
+
98
+ // after the Skra content inside the shadow root has rendered at least once
99
+ registerSkraStyles(shadowRoot);
100
+ // rebuild the cached aggregate if more Skra styles were injected since:
101
+ registerSkraStyles(shadowRoot, { refresh: true });
102
+ ```
103
+
104
+ `registerSkraStyles(shadowRoot, options?)` adopts Skra's **structural** stylesheets
105
+ (per-component Emulated rules incl. the variant matrix, `_baseline`, `_form-controls`,
106
+ `utilities`) into the root. It deliberately **skips the token value layers** — those inherit,
107
+ and re-declaring them inside would freeze shell-driven skin morph (ADR 042 §3.4). Options:
108
+ `marker` (default `/skra/i`), `fallback` (`<style>` injection when constructable stylesheets
109
+ are unavailable, default `true`), `refresh`. There is also `unregisterSkraStyles(shadowRoot)`.
110
+
111
+ **This applies to Angular `ViewEncapsulation.ShadowDom` too**, not only foreign `attachShadow()`
112
+ roots: Angular replicates _component_ styles into a ShadowDom root but NOT the global slices
113
+ (F-12) — so without the bridge, `skra-sr-only` content renders visible (e.g. the datepicker's
114
+ per-day SR labels overflow the grid — F-15), `::backdrop`/scroll-lock are missing (F-7), etc.
115
+
116
+ ## Limits that do NOT cross a boundary (by design)
117
+
118
+ The native top-layer substrate (ADR 0044) has per-context limits — these are architecture, not
119
+ bugs:
120
+
121
+ - **Modal scroll-lock** uses `html:has(dialog[skraDialog]:modal)`, and `:has()` does not pierce
122
+ a shadow boundary — a dialog opened from inside a shadow root does **not** lock the outer page
123
+ scroll (F-6). For shift-free locking when Skra owns the page, set
124
+ `html { scrollbar-gutter: stable }` globally.
125
+ - **`::backdrop` scrim** lives in the `_overlay.scss` global slice; inside a shadow root it
126
+ needs the bridge (F-7).
127
+ - **iframe** — overlays open in the **iframe's own top layer, contained to the frame** (F-11);
128
+ they cannot overlay the parent app. Pass design context into the frame via query params /
129
+ reload. This is a fundamental limit of the per-document top layer, not a defect.