@skra/ui 0.5.0 → 0.7.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 (42) hide show
  1. package/consumer-guide/README.md +90 -85
  2. package/consumer-guide/accessible-names.md +2 -1
  3. package/consumer-guide/custom-components.md +154 -0
  4. package/consumer-guide/feedback.md +100 -0
  5. package/consumer-guide/wrapping-patterns.md +98 -90
  6. package/fesm2022/skra-ui-alert.mjs +55 -6
  7. package/fesm2022/skra-ui-alert.mjs.map +1 -1
  8. package/fesm2022/skra-ui-button.mjs +2 -2
  9. package/fesm2022/skra-ui-button.mjs.map +1 -1
  10. package/fesm2022/skra-ui-checkbox.mjs +2 -2
  11. package/fesm2022/skra-ui-checkbox.mjs.map +1 -1
  12. package/fesm2022/skra-ui-chip.mjs +2 -2
  13. package/fesm2022/skra-ui-chip.mjs.map +1 -1
  14. package/fesm2022/skra-ui-core.mjs +30 -5
  15. package/fesm2022/skra-ui-core.mjs.map +1 -1
  16. package/fesm2022/skra-ui-input.mjs +2 -2
  17. package/fesm2022/skra-ui-input.mjs.map +1 -1
  18. package/fesm2022/skra-ui-menu.mjs +38 -14
  19. package/fesm2022/skra-ui-menu.mjs.map +1 -1
  20. package/fesm2022/skra-ui-popover.mjs +25 -9
  21. package/fesm2022/skra-ui-popover.mjs.map +1 -1
  22. package/fesm2022/skra-ui-radio.mjs +2 -2
  23. package/fesm2022/skra-ui-radio.mjs.map +1 -1
  24. package/fesm2022/skra-ui-rating-display.mjs +2 -2
  25. package/fesm2022/skra-ui-rating-display.mjs.map +1 -1
  26. package/fesm2022/skra-ui-star-rating.mjs +2 -2
  27. package/fesm2022/skra-ui-star-rating.mjs.map +1 -1
  28. package/fesm2022/skra-ui-switch.mjs +2 -2
  29. package/fesm2022/skra-ui-switch.mjs.map +1 -1
  30. package/fesm2022/skra-ui-toast.mjs +2 -2
  31. package/fesm2022/skra-ui-toast.mjs.map +1 -1
  32. package/fesm2022/skra-ui-tooltip.mjs +7 -2
  33. package/fesm2022/skra-ui-tooltip.mjs.map +1 -1
  34. package/package.json +2 -2
  35. package/types/skra-ui-alert.d.ts +39 -2
  36. package/types/skra-ui-alert.d.ts.map +1 -1
  37. package/types/skra-ui-core.d.ts.map +1 -1
  38. package/types/skra-ui-menu.d.ts +18 -6
  39. package/types/skra-ui-menu.d.ts.map +1 -1
  40. package/types/skra-ui-popover.d.ts +13 -4
  41. package/types/skra-ui-popover.d.ts.map +1 -1
  42. package/types/skra-ui-tooltip.d.ts.map +1 -1
@@ -1,85 +1,90 @@
1
- # Consuming Skra — Agent Guide (Core)
2
-
3
- > **DRAFT (2026-06-25).** All 7 sections drafted. Snippets are grounded in source (verified
4
- > against `libs/ui/*`) + the `apps/dogfood` friction journal (`F-n`) + the Iron Boundaries.
5
- > Still draft: not yet referenced from a real consumer repo, not lint-gated against ADR drift,
6
- > and cross-engine/Safari caveats inherit Skra's own open item O-9.
7
-
8
- This guide is for an **agent working inside a repository that consumes Skra** (imports
9
- `@skra/*`), not for authoring Skra itself. It is written to be **copied into / referenced
10
- from a consumer repo**.
11
-
12
- ---
13
-
14
- ## How to use this guide (read this first — it is the point)
15
-
16
- This file is **tiered on purpose** (same model as `FORGE_ADR_SUMMARY.md`). To keep token
17
- cost low:
18
-
19
- 1. **Always load this core** (it is small) — the Iron Rules + the Section Router below.
20
- 2. Then load **at most the ONE section** that matches your current task (the Router maps
21
- task → file). Do **not** read every section "to be safe" — that defeats the design.
22
- 3. Open the full Skra ADR / spec only when a section explicitly points you there.
23
-
24
- ---
25
-
26
- ## Iron Rules of Consumption (the 6 a consumer breaks on day one)
27
-
28
- Violating these is a review blocker in a consumer repo just as inside Skra.
29
-
30
- 1. **Import only from public `@skra/ui/*` entry points** — never a deep relative `libs/...`
31
- path. Skra ships as one package, `@skra/ui`, with a secondary entry point per component
32
- (e.g. `@skra/ui/button`, `@skra/ui/typography`, `@skra/ui/select`); the primary
33
- `@skra/ui` exports nothing. (Full entry-point list → `setup-and-gotchas.md`.)
34
- 2. **You own the value.** Skra form atoms do **not** implement `ControlValueAccessor` /
35
- `FormValueControl` (ADR 037). Native-host atoms take `[formField]`/`[formControl]`/
36
- `[(ngModel)]`; value-owned overlays need an explicit `[value]`/`(valueChange)` bridge.
37
- (How → `forms-and-binding.md`.)
38
- 3. **Accessible name has exactly ONE path** (ADR 033). No Skra atom exposes an `ariaLabel`
39
- input; never put `aria-label` next to visible text on the same host. (Paths →
40
- `accessible-names.md`.)
41
- 4. **Inside `<skra-field>`, do not pass `id` / `for` / `aria-describedby` by hand** — the
42
- DI context wires label↔control↔hint↔error automatically (ADR 013).
43
- 5. **Under Signal Forms, native constraint attributes go in the schema, not the template.**
44
- `name` on grouped radios and `maxlength` / `min` / `pattern` on a `[formField]` host raise
45
- `NG8022` — Signal Forms owns them (F-1/F-2). (Details → `forms-and-binding.md`.)
46
- 6. **Never theme with inline styles.** No `[style.--skra-*]` bindings or `style="…"` (breaks
47
- CSP, ADR 021). Override tokens via a CSS class / `data-skra-*` scope; components are
48
- `margin: 0` (ADR 022) — spacing comes from layout atoms, never a margin on the component.
49
-
50
- ---
51
-
52
- ## Section Router — load the ONE that matches your task
53
-
54
- | Section | File | Load when | Status |
55
- | --------------------------- | ------------------------------ | ----------------------------------------------------------------------------------------------------------- | -------- |
56
- | **Setup & gotchas** | `setup-and-gotchas.md` | First integration; import fails; which `@skra/*` package exports what; boolean-attribute & theming pitfalls | ✅ draft |
57
- | **Forms & value binding** | `forms-and-binding.md` | Wiring an input / select / checkbox / rating to Signal Forms, Reactive Forms, or `ngModel` | ✅ draft |
58
- | **Accessible names** | `accessible-names.md` | Naming buttons, icon-only controls, overlays; using `skra-sr-only` | ✅ draft |
59
- | **Theming & encapsulation** | `theming-and-encapsulation.md` | Skins, dark mode, density; mounting under Shadow DOM / a microfrontend / an iframe | ✅ draft |
60
- | **Overlays** | `overlays.md` | Dialog / tooltip / popover / menu / select in the top layer; shadow & iframe limits | ✅ draft |
61
- | **Layout** | `layout.md` | Spacing & composition with Stack / Box / Surface / Grid; building a card; the gap scale | ✅ draft |
62
- | **Wrapping Skra** | `wrapping-patterns.md` | Building your own higher-level components over Skra atoms (simplified API) | ✅ draft |
63
-
64
- ---
65
-
66
- ## Quick reference — task → section
67
-
68
- | I want to… | Go to |
69
- | -------------------------------------------------------------------------- | ----------------------------------------- |
70
- | Add `@skra/ui/*` to a project and import an atom | `setup-and-gotchas.md` |
71
- | Bind a text input / textarea to a form | `forms-and-binding.md` |
72
- | Bind a `skra-select` / `skra-datepicker` / `skra-star-rating` to a form | `forms-and-binding.md` (the value bridge) |
73
- | Name an icon-only button | `accessible-names.md` |
74
- | Switch skin / dark mode at runtime | `theming-and-encapsulation.md` |
75
- | Mount Skra inside a Web Component / microfrontend | `theming-and-encapsulation.md` |
76
- | Open a dialog or tooltip | `overlays.md` |
77
- | Space components apart | `layout.md` |
78
- | Build a card / panel / tile (there is no `skra-card`) | `layout.md` (Surface) |
79
- | Wrap a Skra atom in a project-specific component | `wrapping-patterns.md` |
80
-
81
- ---
82
-
83
- _Source of truth for the rules: Skra `FORGE_ADR_SUMMARY.md` + `adr/decisions/`. Real
84
- consumption evidence: `apps/dogfood/FRICTION_JOURNAL.md`. This guide restates the consumer
85
- slice of those — on divergence, the ADRs win._
1
+ # Consuming Skra — Agent Guide (Core)
2
+
3
+ > **DRAFT (2026-06-25; 8th section added 2026-08-30; 9th — Feedback — 2026-09-27).** All 9 sections drafted. Snippets are grounded in source (verified
4
+ > against `libs/ui/*`) + the `apps/dogfood` friction journal (`F-n`) + the Iron Boundaries.
5
+ > Still draft: not yet referenced from a real consumer repo, not lint-gated against ADR drift,
6
+ > and cross-engine/Safari caveats inherit Skra's own open item O-9.
7
+
8
+ This guide is for an **agent working inside a repository that consumes Skra** (imports
9
+ `@skra/*`), not for authoring Skra itself. It is written to be **copied into / referenced
10
+ from a consumer repo**.
11
+
12
+ ---
13
+
14
+ ## How to use this guide (read this first — it is the point)
15
+
16
+ This file is **tiered on purpose** (same model as `FORGE_ADR_SUMMARY.md`). To keep token
17
+ cost low:
18
+
19
+ 1. **Always load this core** (it is small) — the Iron Rules + the Section Router below.
20
+ 2. Then load **at most the ONE section** that matches your current task (the Router maps
21
+ task → file). Do **not** read every section "to be safe" — that defeats the design.
22
+ 3. Open the full Skra ADR / spec only when a section explicitly points you there.
23
+
24
+ ---
25
+
26
+ ## Iron Rules of Consumption (the 6 a consumer breaks on day one)
27
+
28
+ Violating these is a review blocker in a consumer repo just as inside Skra.
29
+
30
+ 1. **Import only from public `@skra/ui/*` entry points** — never a deep relative `libs/...`
31
+ path. Skra ships as one package, `@skra/ui`, with a secondary entry point per component
32
+ (e.g. `@skra/ui/button`, `@skra/ui/typography`, `@skra/ui/select`); the primary
33
+ `@skra/ui` exports nothing. (Full entry-point list → `setup-and-gotchas.md`.)
34
+ 2. **You own the value.** Skra form atoms do **not** implement `ControlValueAccessor` /
35
+ `FormValueControl` (ADR 037). Native-host atoms take `[formField]`/`[formControl]`/
36
+ `[(ngModel)]`; value-owned overlays need an explicit `[value]`/`(valueChange)` bridge.
37
+ (How → `forms-and-binding.md`.)
38
+ 3. **Accessible name has exactly ONE path** (ADR 033). No Skra atom exposes an `ariaLabel`
39
+ input; never put `aria-label` next to visible text on the same host. (Paths →
40
+ `accessible-names.md`.)
41
+ 4. **Inside `<skra-field>`, do not pass `id` / `for` / `aria-describedby` by hand** — the
42
+ DI context wires label↔control↔hint↔error automatically (ADR 013).
43
+ 5. **Under Signal Forms, native constraint attributes go in the schema, not the template.**
44
+ `name` on grouped radios and `maxlength` / `min` / `pattern` on a `[formField]` host raise
45
+ `NG8022` — Signal Forms owns them (F-1/F-2). (Details → `forms-and-binding.md`.)
46
+ 6. **Never theme with inline styles.** No `[style.--skra-*]` bindings or `style="…"` (breaks
47
+ CSP, ADR 021). Override tokens via a CSS class / `data-skra-*` scope; components are
48
+ `margin: 0` (ADR 022) — spacing comes from layout atoms, never a margin on the component.
49
+
50
+ ---
51
+
52
+ ## Section Router — load the ONE that matches your task
53
+
54
+ | Section | File | Load when | Status |
55
+ | --------------------------- | ------------------------------ | ---------------------------------------------------------------------------------------------------------------------- | -------- |
56
+ | **Setup & gotchas** | `setup-and-gotchas.md` | First integration; import fails; which `@skra/*` package exports what; boolean-attribute & theming pitfalls | ✅ draft |
57
+ | **Forms & value binding** | `forms-and-binding.md` | Wiring an input / select / checkbox / rating to Signal Forms, Reactive Forms, or `ngModel` | ✅ draft |
58
+ | **Accessible names** | `accessible-names.md` | Naming buttons, icon-only controls, overlays; using `skra-sr-only` | ✅ draft |
59
+ | **Theming & encapsulation** | `theming-and-encapsulation.md` | Skins, dark mode, density; mounting under Shadow DOM / a microfrontend / an iframe | ✅ draft |
60
+ | **Feedback** | `feedback.md` | Alert vs toast vs badge vs field error; `live` announcing; dismissal; an alert with a next-step action | ✅ draft |
61
+ | **Overlays** | `overlays.md` | Dialog / tooltip / popover / menu / select in the top layer; shadow & iframe limits | ✅ draft |
62
+ | **Layout** | `layout.md` | Spacing & composition with Stack / Box / Surface / Grid; building a card; the gap scale | ✅ draft |
63
+ | **Wrapping Skra** | `wrapping-patterns.md` | Building your own higher-level components over Skra atoms (simplified API) | ✅ draft |
64
+ | **Custom components** | `custom-components.md` | Authoring a net-new widget Skra does not ship (nothing to wrap) on Skra tokens — own token tier, state, promotion path | ✅ draft |
65
+
66
+ ---
67
+
68
+ ## Quick reference — task → section
69
+
70
+ | I want to… | Go to |
71
+ | ----------------------------------------------------------------------- | ----------------------------------------- |
72
+ | Add `@skra/ui/*` to a project and import an atom | `setup-and-gotchas.md` |
73
+ | Bind a text input / textarea to a form | `forms-and-binding.md` |
74
+ | Bind a `skra-select` / `skra-datepicker` / `skra-star-rating` to a form | `forms-and-binding.md` (the value bridge) |
75
+ | Name an icon-only button | `accessible-names.md` |
76
+ | Switch skin / dark mode at runtime | `theming-and-encapsulation.md` |
77
+ | Mount Skra inside a Web Component / microfrontend | `theming-and-encapsulation.md` |
78
+ | Show an alert / toast / badge, or add a "Try again" button to an alert | `feedback.md` |
79
+ | Open a dialog or tooltip | `overlays.md` |
80
+ | Space components apart | `layout.md` |
81
+ | Build a card / panel / tile (there is no `skra-card`) | `layout.md` (Surface) |
82
+ | Wrap a Skra atom in a project-specific component | `wrapping-patterns.md` |
83
+ | Build a widget Skra does not ship (scrubber, timeline, domain visual) | `custom-components.md` |
84
+ | Keep an app-local component on-skin, on-density, dark-mode-correct | `custom-components.md` |
85
+
86
+ ---
87
+
88
+ _Source of truth for the rules: Skra `FORGE_ADR_SUMMARY.md` + `adr/decisions/`. Real
89
+ consumption evidence: `apps/dogfood/FRICTION_JOURNAL.md`. This guide restates the consumer
90
+ slice of those — on divergence, the ADRs win._
@@ -42,7 +42,8 @@ must be loaded (and bridged into any shadow root — see `theming-and-encapsulat
42
42
  or the text renders visibly.
43
43
 
44
44
  > The same pattern names other built-in sr-only slots, e.g. `[skra-alert-close-label]` on
45
- > `skra-alert`'s dismiss button.
45
+ > `skra-alert`'s dismiss button. An alert **action** (`[skra-alert-actions]`) names itself by its
46
+ > visible text (Path 1); in a live alert that label is announced with the message (`feedback.md`).
46
47
 
47
48
  ## Path 3 — `aria-label` on an icon-only host
48
49
 
@@ -0,0 +1,154 @@
1
+ # Building Your Own Component on Skra Tokens
2
+
3
+ > Load this for: authoring a **net-new widget that Skra does not ship and that has no Skra atom
4
+ > to wrap** — a scrubber, a timeline, a domain visual — while keeping it inside the design
5
+ > system's guarantees (skin, dark scheme, density, contrast, CSP).
6
+ >
7
+ > If you are simplifying the API of an atom Skra _does_ ship, you want `wrapping-patterns.md`
8
+ > instead. This page is for the case where there is nothing to wrap.
9
+
10
+ ## Step 0 — decompose before you build
11
+
12
+ Most "I need a component Skra doesn't have" cases are not one component. Split the widget into
13
+ three buckets first, because two of them are already solved:
14
+
15
+ | Bucket | Where it belongs |
16
+ | ------------------------------------------------------------------------ | ----------------------------------------------------------------------- |
17
+ | Parts that ARE Skra atoms — buttons, icons, text, a painted container | `@skra/ui` (`skraButton`, `skra-icon`, `skraTypography`, `skraSurface`) |
18
+ | Domain logic — media elements, state machines, data fetching, formatting | your app's services/stores. Not a design-system concern at any level |
19
+ | The genuinely novel visual — the part with no atom and no analogue | this page |
20
+
21
+ The third bucket is usually much smaller than it first looks. Build only that.
22
+
23
+ ## The rule — own your tier 3, resolve it to tier 2
24
+
25
+ Skra's token engine has four layers: **1 `ref`** (raw values) → **2 `sys`** (semantic) →
26
+ **3 `comp`** (component-scoped) → **4 API** (per-instance overrides).
27
+
28
+ Layer 3 can only be _minted_ inside the Skra repo, because a `--skra-comp-*` token needs a value
29
+ in every skin. That does **not** mean your component drops to layer 2. It means your component
30
+ **owns its own layer 3**, in its own namespace, with defaults that resolve to layer 2:
31
+
32
+ ```scss
33
+ /* app-scrubber.component.scss */
34
+ :host {
35
+ /* Your tier 3 — one block, the only place a value is decided. */
36
+ --app-scrubber-track-background: var(--skra-sys-color-surface-muted);
37
+ --app-scrubber-track-radius: var(--skra-sys-radius-full);
38
+ --app-scrubber-track-size: var(--skra-sys-spacing-2);
39
+ --app-scrubber-thumb-background: var(--skra-sys-color-primary-base);
40
+ --app-scrubber-thumb-size: var(--skra-sys-size-interactive-sm);
41
+ --app-scrubber-padding-inline: var(--skra-sys-spacing-3);
42
+ --app-scrubber-focus-ring-color: var(--skra-sys-color-focus-ring);
43
+ --app-scrubber-focus-ring-width: 0.125rem; /* no sys token for this — core keeps it literal too */
44
+
45
+ display: block;
46
+ margin: 0; /* ADR 022 — outer spacing is the container's job, never the component's */
47
+ padding-inline: var(--app-scrubber-padding-inline);
48
+ }
49
+
50
+ /* State comes from an attribute, never from a bound style (ADR 021 — CSP-safe). */
51
+ :host(:hover) {
52
+ --app-scrubber-thumb-background: var(--skra-sys-color-primary-base-hover);
53
+ }
54
+ :host([data-app-dragging]) {
55
+ --app-scrubber-thumb-background: var(--skra-sys-color-primary-base-active);
56
+ }
57
+ :host([data-app-disabled]) {
58
+ --app-scrubber-track-background: var(--skra-sys-color-neutral-soft);
59
+ --app-scrubber-thumb-background: var(--skra-sys-color-neutral-base);
60
+ }
61
+
62
+ .app-scrubber-track {
63
+ background: var(--app-scrubber-track-background);
64
+ border-radius: var(--app-scrubber-track-radius);
65
+ block-size: var(--app-scrubber-track-size);
66
+ }
67
+ .app-scrubber-thumb {
68
+ background: var(--app-scrubber-thumb-background);
69
+ inline-size: var(--app-scrubber-thumb-size);
70
+ block-size: var(--app-scrubber-thumb-size);
71
+ }
72
+ .app-scrubber-thumb:focus-visible {
73
+ outline: var(--app-scrubber-focus-ring-width) solid var(--app-scrubber-focus-ring-color);
74
+ }
75
+ ```
76
+
77
+ ```ts
78
+ // app-scrubber.component.ts — state reaches CSS as attributes, not as style bindings.
79
+ @Component({
80
+ selector: 'app-scrubber',
81
+ host: {
82
+ '[attr.data-app-dragging]': 'dragging() ? "" : null',
83
+ '[attr.data-app-disabled]': 'disabled() ? "" : null',
84
+ },
85
+ // …
86
+ })
87
+ ```
88
+
89
+ Why not read `sys` directly at each property? Because then the value is scattered across the
90
+ file, per-instance overrides have nothing to target, and promoting the widget into Skra later
91
+ becomes a rewrite instead of a rename.
92
+
93
+ **Namespace:** use **your own prefix** (`--app-*`, `--acme-*`). Do **not** declare
94
+ `--skra-comp-*` or `--skra-sys-*` values of your own — that namespace belongs to the token
95
+ engine, a skin may overwrite it at any time, and a future Skra component of the same name
96
+ would collide with you.
97
+
98
+ ## What you get for free
99
+
100
+ Everything below follows from resolving your defaults to `sys` — no work on your side:
101
+
102
+ | Capability | Why it works |
103
+ | ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
104
+ | **Every skin** | `sys` is redefined per skin; your widget re-paints when the skin changes |
105
+ | **Dark scheme** | the dark layer redefines the same `sys` names |
106
+ | **Density** | `[data-skra-density]` remaps `--skra-sys-spacing-*` and `--skra-sys-size-interactive-*` at the **sys** layer — so a widget sized from those follows density automatically. Hardcoding `padding: 0.5rem` opts you out |
107
+ | **Interaction states** | the build synthesises `-hover` / `-active` for `sys` colours too (`--skra-sys-color-primary-base-hover`), scaled by each skin's interaction profile |
108
+ | **Shadow DOM / CSP** | custom properties inherit across shadow boundaries; attribute-driven state needs no inline styles |
109
+
110
+ ## What you do NOT get — plan around it
111
+
112
+ 1. **No build-time contrast gate on your pairs.** Skra fails its own build on a bad
113
+ foreground/background pair; your widget's pairs are not measured. Prefer pairs Skra already
114
+ gates (`*-strong` / `*-soft`, `on-*` foregrounds) over improvised combinations, and check
115
+ any novel pair manually.
116
+ 2. **No state synthesis from a colour of your own.** You cannot generate a hover ramp from a
117
+ bespoke base — pick a `sys` colour that already ships `-hover` / `-active`. In practice this
118
+ is a healthy constraint: it keeps you inside the skin's palette.
119
+ 3. **No skin-surface manifest.** Your tokens are invisible to skin/preset tooling.
120
+ 4. **No per-skin bespoke treatment.** Your widget inherits what flows through `sys` (each
121
+ skin's own radius scale, palette and interaction strength) — not a hand-tuned variant.
122
+
123
+ ## The floor — non-negotiable, whatever the widget
124
+
125
+ These are the rules that make a component _reusable_ rather than _copy-pasteable_. A hardcoded
126
+ `#FFD700` cannot be re-skinned by your next app; that is the whole difference.
127
+
128
+ - **Every value maps to a token.** No colour literals, no magic pixel values where a
129
+ `--skra-sys-*` exists. Structural constants (a `1.4s` pulse, a `50%` split) are fine.
130
+ - **CSP-safe state.** State reflects as an attribute on the host and is styled by an attribute
131
+ selector. Never `[style.--var]="signal()"`, never runtime style mutation.
132
+ - **Zero outer margin.** `margin: 0` on the host; spacing between components belongs to the
133
+ layout atoms (`skraStack` / `skraBox`).
134
+ - **Accessible name and keyboard behaviour before paint.** Follow the WAI-ARIA APG pattern the
135
+ widget actually is; see `accessible-names.md`.
136
+ - **No `!important`, no `::ng-deep`, no skin names in selectors.**
137
+
138
+ ## Promotion path — when the widget outgrows the app
139
+
140
+ Location is deliberately reversible: a widget whose defaults resolve to `sys` moves without a
141
+ single token change.
142
+
143
+ 1. **App-local** — the default, and where it stays while exactly one app uses it.
144
+ 2. **Your own shared library** (`@yourorg/ui`, built ON Skra) — when a **second** app genuinely
145
+ needs it. Do not build the shared package for a widget that exists in one app; that is
146
+ speculative generality.
147
+ 3. **Skra core** — only if it stops being domain-specific: no domain vocabulary, no third-party
148
+ runtime dependency, and a consumer in a _different_ domain would use it as-is. That is a
149
+ feature request against Skra, not something you can do from outside.
150
+
151
+ ---
152
+
153
+ _See also: `wrapping-patterns.md` (simplifying a Skra atom's API), `theming-and-encapsulation.md`
154
+ (loading token layers, skin/scheme/density switching), `accessible-names.md`, `layout.md`._
@@ -0,0 +1,100 @@
1
+ # Feedback — Alerts, Toasts, Badges
2
+
3
+ > Load this for: telling the user what happened — an in-page alert, a transient toast, a status
4
+ > badge — and putting a next step ("Try again") inside an alert.
5
+ > Governed by ADR 0052 (live-region semantics), ADR 035 (intent / emphasis) and ADR 0059
6
+ > (actions on an intent-tinted ground).
7
+
8
+ ## Which one?
9
+
10
+ | You need to… | Use |
11
+ | ---------------------------------------------------------- | ---------------------------------------------------------------------- |
12
+ | Say something about a page or a section, and keep it there | `<skra-alert>` (`@skra/ui/alert`) |
13
+ | Confirm an action briefly without moving focus ("Saved") | `<skra-toast>` inside one `<skra-toast-region>` (`@skra/ui/toast`) |
14
+ | Label a thing with a short status ("Draft", "3 new") | `<skra-badge>` (`@skra/ui/badge`) |
15
+ | Say what is wrong with ONE form value | `<skra-error>` inside `<skra-field>` (`@skra/ui/field`) — not an alert |
16
+
17
+ The alert, the toast and the badge take the same two look-free axes (`<skra-error>` is always an error): `intent` (`neutral` · `info` · `success` · `warning` ·
18
+ `danger`) and `emphasis` (`high` = filled, `medium` = soft; the default). Put the severity in the
19
+ **text** too — colour and icon alone do not carry meaning (WCAG 1.4.1).
20
+
21
+ ## Announcing (`live`)
22
+
23
+ `live` decides whether a screen reader announces the content when it appears; it maps to the
24
+ implicit `role` (`polite` → `status`, `assertive` → `alert`). It is about **how the content
25
+ arrives**, not how severe it is:
26
+
27
+ - present when the page loads → leave `live="off"` (alert default);
28
+ - inserted after the user did something → `live="polite"`, or `"assertive"` for a failure that
29
+ blocks them.
30
+
31
+ Skra sets the role only — never add `aria-live` / `aria-atomic` yourself. A live region announces
32
+ its whole text, **including the labels of the controls inside it** (the dismiss label, an action).
33
+
34
+ ## Dismissal is yours
35
+
36
+ `dismissible` renders a close control and emits `(dismissed)`. The alert does **not** remove
37
+ itself — you do, with `@if`, the same way you showed it. Name the control with a projected
38
+ `[skra-alert-close-label]` (visually hidden text; `accessible-names.md`). If the close control had
39
+ focus, move focus somewhere sensible before you remove the alert.
40
+
41
+ ```html
42
+ @if (saveFailed()) {
43
+ <skra-alert intent="danger" live="assertive" dismissible (dismissed)="saveFailed.set(false)">
44
+ Could not save.
45
+ <span skra-alert-close-label class="skra-sr-only">Dismiss</span>
46
+ </skra-alert>
47
+ }
48
+ ```
49
+
50
+ ## An alert with an action
51
+
52
+ Mark each next-step button `[skra-alert-actions]`. The alert places the actions **below the
53
+ message**, as a row that wraps on a narrow container, and never lets them collide with the close
54
+ control. Several actions are fine; they keep your order.
55
+
56
+ ```html
57
+ <skra-alert intent="danger" live="assertive">
58
+ <span skra-alert-title>Could not save</span>
59
+ Your changes are kept on this device. Check your connection and try again.
60
+ <button skraButton skra-alert-actions emphasis="medium" type="button" (click)="retry()">Try again</button>
61
+ </skra-alert>
62
+ ```
63
+
64
+ **The action keeps its own meaning.** The alert does not pass its `intent` to the button. A retry
65
+ inside a `danger` alert is not a destructive action, so it must not be a `danger` button — that
66
+ colour means "this deletes something". Choose the button's `intent` for what the button **does**,
67
+ exactly as anywhere else.
68
+
69
+ **What is guaranteed legible on the alert's colour.** Skra measures these combinations in every
70
+ state (rest, hover, pressed, focus), both colour schemes and every shipped skin:
71
+
72
+ | Alert | Guaranteed actions |
73
+ | ----------------------------- | ------------------------------------------------------------------------------- |
74
+ | `emphasis="medium"` (default) | a `neutral` button at any emphasis; a button of any intent at `emphasis="high"` |
75
+ | `emphasis="high"` (filled) | a button of any intent at `emphasis="high"` **other than the alert's own** |
76
+
77
+ - The zero-config `<button skraButton skra-alert-actions>` (`neutral`, `high`) is guaranteed on
78
+ every alert except a `neutral` + `high` one.
79
+ - **Not supported:** a coloured `medium`/`low` button; a `medium`/`low` button on a filled alert
80
+ (it measured as low as 1.2:1); a `high` button in the alert's own intent (it has the alert's
81
+ exact colour — no visible button). Nothing stops you writing them; nothing guarantees them either.
82
+ - Only `button[skraButton]` is measured in the actions row. A link or a badge there is on you.
83
+ - The focus ring of the actions follows the alert (on a filled alert it switches to the alert's
84
+ text colour, because the page ring disappears on the fill). To re-colour it, target the alert's
85
+ per-variant hook `--skra-comp-alert-focus-ring-color-<intent>-<emphasis>` in a skin overlay preset
86
+ (the only tier its manifest allows) — never the button's ring.
87
+ - An action that **navigates** is still a `<button>` that routes — Skra has no link styled as a
88
+ button (ADR 032).
89
+
90
+ ## Toasts
91
+
92
+ One `<skra-toast-region>` near the app root announces every toast from persistent live regions;
93
+ each `<skra-toast>` sets `intent`, `emphasis`, `live` (default `polite`), `duration` (ms; pauses on
94
+ hover / focus) and `dismissible` → `(dismissed)`. Removal is yours, as for the alert. Toasts do
95
+ not take actions yet.
96
+
97
+ ## Badges
98
+
99
+ `<skra-badge>` is a small, inline, non-interactive status label (`intent`, `emphasis`, optional
100
+ `[skra-badge-icon]`). Leave `live="off"` unless the badge changes while the user watches it.