@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.
- package/consumer-guide/README.md +90 -85
- package/consumer-guide/accessible-names.md +2 -1
- package/consumer-guide/custom-components.md +154 -0
- package/consumer-guide/feedback.md +100 -0
- package/consumer-guide/wrapping-patterns.md +98 -90
- package/fesm2022/skra-ui-alert.mjs +55 -6
- package/fesm2022/skra-ui-alert.mjs.map +1 -1
- package/fesm2022/skra-ui-button.mjs +2 -2
- package/fesm2022/skra-ui-button.mjs.map +1 -1
- package/fesm2022/skra-ui-checkbox.mjs +2 -2
- package/fesm2022/skra-ui-checkbox.mjs.map +1 -1
- package/fesm2022/skra-ui-chip.mjs +2 -2
- package/fesm2022/skra-ui-chip.mjs.map +1 -1
- package/fesm2022/skra-ui-core.mjs +30 -5
- package/fesm2022/skra-ui-core.mjs.map +1 -1
- package/fesm2022/skra-ui-input.mjs +2 -2
- package/fesm2022/skra-ui-input.mjs.map +1 -1
- package/fesm2022/skra-ui-menu.mjs +38 -14
- package/fesm2022/skra-ui-menu.mjs.map +1 -1
- package/fesm2022/skra-ui-popover.mjs +25 -9
- package/fesm2022/skra-ui-popover.mjs.map +1 -1
- package/fesm2022/skra-ui-radio.mjs +2 -2
- package/fesm2022/skra-ui-radio.mjs.map +1 -1
- package/fesm2022/skra-ui-rating-display.mjs +2 -2
- package/fesm2022/skra-ui-rating-display.mjs.map +1 -1
- package/fesm2022/skra-ui-star-rating.mjs +2 -2
- package/fesm2022/skra-ui-star-rating.mjs.map +1 -1
- package/fesm2022/skra-ui-switch.mjs +2 -2
- package/fesm2022/skra-ui-switch.mjs.map +1 -1
- package/fesm2022/skra-ui-toast.mjs +2 -2
- package/fesm2022/skra-ui-toast.mjs.map +1 -1
- package/fesm2022/skra-ui-tooltip.mjs +7 -2
- package/fesm2022/skra-ui-tooltip.mjs.map +1 -1
- package/package.json +2 -2
- package/types/skra-ui-alert.d.ts +39 -2
- package/types/skra-ui-alert.d.ts.map +1 -1
- package/types/skra-ui-core.d.ts.map +1 -1
- package/types/skra-ui-menu.d.ts +18 -6
- package/types/skra-ui-menu.d.ts.map +1 -1
- package/types/skra-ui-popover.d.ts +13 -4
- package/types/skra-ui-popover.d.ts.map +1 -1
- package/types/skra-ui-tooltip.d.ts.map +1 -1
package/consumer-guide/README.md
CHANGED
|
@@ -1,85 +1,90 @@
|
|
|
1
|
-
# Consuming Skra — Agent Guide (Core)
|
|
2
|
-
|
|
3
|
-
> **DRAFT (2026-06-25).** All
|
|
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
|
|
55
|
-
| --------------------------- | ------------------------------ |
|
|
56
|
-
| **Setup & gotchas** | `setup-and-gotchas.md` | First integration; import fails; which `@skra/*` package exports what; boolean-attribute & theming pitfalls
|
|
57
|
-
| **Forms & value binding** | `forms-and-binding.md` | Wiring an input / select / checkbox / rating to Signal Forms, Reactive Forms, or `ngModel`
|
|
58
|
-
| **Accessible names** | `accessible-names.md` | Naming buttons, icon-only controls, overlays; using `skra-sr-only`
|
|
59
|
-
| **Theming & encapsulation** | `theming-and-encapsulation.md` | Skins, dark mode, density; mounting under Shadow DOM / a microfrontend / an iframe
|
|
60
|
-
| **
|
|
61
|
-
| **
|
|
62
|
-
| **
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
|
71
|
-
|
|
|
72
|
-
|
|
|
73
|
-
|
|
|
74
|
-
|
|
|
75
|
-
|
|
|
76
|
-
|
|
|
77
|
-
|
|
|
78
|
-
|
|
|
79
|
-
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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.
|