@ponchia/ui 0.7.0 → 0.8.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 (52) hide show
  1. package/CHANGELOG.md +133 -0
  2. package/README.md +3 -3
  3. package/classes/classes.json +79 -7
  4. package/classes/index.d.ts +53 -1
  5. package/classes/index.js +60 -0
  6. package/classes/vscode.css-custom-data.json +24 -0
  7. package/css/app.css +14 -3
  8. package/css/disclosure.css +7 -7
  9. package/css/feedback.css +11 -8
  10. package/css/forms.css +3 -3
  11. package/css/navigation.css +1 -1
  12. package/css/overlay.css +19 -5
  13. package/css/primitives.css +101 -6
  14. package/css/site.css +11 -5
  15. package/css/skins.css +97 -3
  16. package/css/state.css +161 -0
  17. package/css/table.css +1 -1
  18. package/css/tokens.css +6 -0
  19. package/css/workbench.css +151 -0
  20. package/dist/bronto.css +1 -1
  21. package/dist/css/app.css +1 -1
  22. package/dist/css/disclosure.css +1 -1
  23. package/dist/css/feedback.css +1 -1
  24. package/dist/css/forms.css +1 -1
  25. package/dist/css/navigation.css +1 -1
  26. package/dist/css/overlay.css +1 -1
  27. package/dist/css/primitives.css +1 -1
  28. package/dist/css/report-kit.css +1 -1
  29. package/dist/css/site.css +1 -1
  30. package/dist/css/skins.css +1 -1
  31. package/dist/css/state.css +1 -1
  32. package/dist/css/table.css +1 -1
  33. package/dist/css/tokens.css +1 -1
  34. package/dist/css/workbench.css +1 -1
  35. package/docs/adr/0001-color-system.md +32 -3
  36. package/docs/contrast.md +102 -18
  37. package/docs/reference.md +52 -1
  38. package/docs/reporting.md +8 -8
  39. package/docs/stability.md +31 -2
  40. package/docs/state.md +51 -1
  41. package/docs/theming.md +18 -0
  42. package/docs/usage.md +62 -2
  43. package/docs/workbench.md +83 -4
  44. package/llms.txt +1 -1
  45. package/package.json +1 -1
  46. package/tokens/figma.variables.json +84 -0
  47. package/tokens/index.d.ts +2 -2
  48. package/tokens/index.js +23 -0
  49. package/tokens/index.json +12 -0
  50. package/tokens/resolved.json +6 -0
  51. package/tokens/skins.js +117 -7
  52. package/tokens/tokens.dtcg.json +7 -1
@@ -1 +1 @@
1
- @layer bronto{.ui-inspector{background: var(--panel);border: 1px solid var(--line);border-radius: var(--radius-md);display: grid;gap: var(--space-sm);padding: var(--space-md)}.ui-inspector__head{align-items: baseline;border-block-end: 1px solid var(--line);display: flex;gap: var(--space-sm);justify-content: space-between;padding-block-end: var(--space-xs)}.ui-inspector__body{display: grid;gap: var(--space-2xs)}.ui-toolstrip{align-items: center;background: var(--panel-strong);border: 1px solid var(--line-strong);border-radius: var(--radius-md);display: flex;flex-wrap: wrap;gap: var(--space-xs);min-inline-size: 0;padding: 0.35rem}.ui-toolstrip--floating{box-shadow: var(--shadow-raised)}.ui-toolstrip--compact{gap: var(--space-2xs);padding: 0.25rem}.ui-toolstrip__brand{align-items: baseline;display: inline-flex;flex: 0 1 auto;gap: var(--space-xs);min-inline-size: 0;padding-inline: var(--space-xs)}.ui-toolstrip__brand strong{color: var(--text);font-size: var(--text-sm)}.ui-toolstrip__context{color: var(--text-dim);font-size: var(--text-xs);min-inline-size: 0;overflow: hidden;text-overflow: ellipsis;white-space: nowrap}.ui-toolstrip__group,.ui-toolstrip__actions{align-items: center;display: inline-flex;flex-wrap: wrap;gap: var(--space-2xs)}.ui-toolstrip__group{background: var(--panel-soft);border: 1px solid var(--line);border-radius: var(--radius-md);padding: 0.16rem}.ui-toolstrip__search{flex: 1 1 14rem;min-inline-size: min(100%,11rem)}.ui-toolstrip__actions{margin-inline-start: auto}.ui-segmented-buttons{align-items: center;background: var(--panel-soft);border: 1px solid var(--line);border-radius: var(--radius-md);display: inline-flex;flex-wrap: wrap;gap: 0.16rem;padding: 0.16rem}.ui-segmented-buttons__button{background: transparent;border: 1px solid transparent;border-radius: var(--radius-sm);color: var(--text-soft);cursor: pointer;font-family: var(--mono);font-size: var(--text-xs);font-weight: 700;letter-spacing: var(--tracking-wide);line-height: 1;min-block-size: 1.9rem;padding: 0.48rem 0.7rem;text-transform: uppercase}.ui-segmented-buttons__button[aria-pressed='true'],.ui-segmented-buttons__button.is-active{background: var(--accent);border-color: var(--accent);color: var(--button-text)}.ui-segmented-buttons__button:disabled,.ui-segmented-buttons__button[aria-disabled='true']{cursor: not-allowed;opacity: 0.45}.ui-property{align-items: baseline;display: grid;gap: var(--space-sm);grid-template-columns: minmax(6rem,38%) 1fr}.ui-property__label{color: var(--text-dim);font-family: var(--mono);font-size: var(--text-2xs);letter-spacing: var(--tracking-wide);text-transform: uppercase}.ui-property__value{color: var(--text);font-size: var(--text-sm);min-inline-size: 0}.ui-selectionbar{align-items: center;background: var(--panel-strong);border: 1px solid var(--line-strong);border-radius: var(--radius-md);box-shadow: var(--shadow-raised);display: flex;flex-wrap: wrap;gap: var(--space-sm) var(--space-md);justify-content: space-between;padding: 0.5rem 0.85rem}.ui-selectionbar__count{color: var(--text);font-family: var(--mono);font-size: var(--text-sm)}.ui-selectionbar__actions{display: flex;flex-wrap: wrap;gap: var(--space-xs)}@media (hover: hover){.ui-segmented-buttons__button:hover:not(:disabled,[aria-disabled='true'],[aria-pressed='true'],.is-active){background: var(--panel);border-color: var(--line-strong);color: var(--text)}}.ui-splitter{--splitter-pos: 50%;--splitter-handle: 0.75rem;--splitter-pane-min: 0px;background: var(--panel);border: 1px solid var(--line);border-radius: var(--radius-md);display: grid;grid-template-columns: minmax(var(--splitter-pane-min),var(--splitter-pos)) var(--splitter-handle) minmax(0,1fr);min-block-size: 18rem;overflow: hidden}.ui-splitter--vertical{grid-template-columns: minmax(var(--splitter-pane-min),var(--splitter-pos)) var(--splitter-handle) minmax(0,1fr)}.ui-splitter--horizontal{grid-template-columns: 1fr;grid-template-rows: minmax(var(--splitter-pane-min),var(--splitter-pos)) var(--splitter-handle) minmax(0,1fr)}.ui-splitter__pane{min-block-size: 0;min-inline-size: 0;overflow: auto;padding: var(--space-md)}.ui-splitter__handle{align-self: stretch;background: var(--panel-strong);border-block: 0;border-inline: 1px solid var(--line);cursor: col-resize;display: grid;min-inline-size: var(--splitter-handle);place-items: center;touch-action: none}.ui-splitter__handle::before{background: var(--line-strong);border-radius: var(--radius-pill);block-size: 2.5rem;content: "";inline-size: 2px}.ui-splitter__handle:hover::before,.ui-splitter__handle.is-active::before,.ui-splitter__handle[aria-valuenow]:focus-visible::before{background: var(--accent)}.ui-splitter__handle:focus-visible{outline: 2px solid var(--focus-ring);outline-offset: -2px}.ui-splitter--horizontal .ui-splitter__handle{border-block: 1px solid var(--line);border-inline: 0;cursor: row-resize;min-block-size: var(--splitter-handle);min-inline-size: 0}.ui-splitter--horizontal .ui-splitter__handle::before{block-size: 2px;inline-size: 2.5rem}@media (forced-colors: active){.ui-segmented-buttons__button[aria-pressed='true'],.ui-segmented-buttons__button.is-active{background: Highlight;border-color: Highlight;color: HighlightText}.ui-splitter__handle{border-color: CanvasText}.ui-splitter__handle::before{background: CanvasText}}}
1
+ @layer bronto{.ui-inspector{background: var(--panel);border: 1px solid var(--line);border-radius: var(--radius-md);display: grid;gap: var(--space-sm);padding: var(--space-md)}.ui-inspector__head{align-items: baseline;border-block-end: 1px solid var(--line);display: flex;gap: var(--space-sm);justify-content: space-between;padding-block-end: var(--space-xs)}.ui-inspector__body{display: grid;gap: var(--space-2xs)}.ui-toolstrip{align-items: center;background: var(--panel-strong);border: 1px solid var(--line-strong);border-radius: var(--radius-md);display: flex;flex-wrap: wrap;gap: var(--space-xs);min-inline-size: 0;padding: 0.35rem}.ui-toolstrip--floating{box-shadow: var(--shadow-raised)}.ui-toolstrip--compact{gap: var(--space-2xs);padding: 0.25rem}.ui-toolstrip--pane{background: none;border: 0;border-block-end: 1px solid var(--line);border-radius: 0;flex-wrap: nowrap;gap: var(--space-2xs);overflow-x: auto;padding: 0.3rem var(--space-xs);scrollbar-width: thin}.ui-toolstrip__fill{flex: 1 1 auto;min-inline-size: 0;overflow: hidden;text-overflow: ellipsis;white-space: nowrap}.ui-toolstrip__brand{align-items: baseline;display: inline-flex;flex: 0 1 auto;gap: var(--space-xs);min-inline-size: 0;padding-inline: var(--space-xs)}.ui-toolstrip__brand strong{color: var(--text);font-size: var(--text-sm)}.ui-toolstrip__context{color: var(--text-dim);font-size: var(--text-xs);min-inline-size: 0;overflow: hidden;text-overflow: ellipsis;white-space: nowrap}.ui-toolstrip__group,.ui-toolstrip__actions{align-items: center;display: inline-flex;flex-wrap: wrap;gap: var(--space-2xs)}.ui-toolstrip__group{background: var(--panel-soft);border: 1px solid var(--line);border-radius: var(--radius-md);padding: 0.16rem}.ui-toolstrip__search{flex: 1 1 14rem;min-inline-size: min(100%,11rem)}.ui-toolstrip__actions{margin-inline-start: auto}.ui-segmented-buttons{align-items: center;background: var(--panel-soft);border: 1px solid var(--line);border-radius: var(--radius-md);display: inline-flex;flex-wrap: wrap;gap: 0.16rem;padding: 0.16rem}.ui-segmented-buttons__button{background: transparent;border: 1px solid transparent;border-radius: var(--radius-sm);color: var(--text-soft);cursor: pointer;font-family: var(--mono);font-size: var(--text-xs);font-weight: 700;letter-spacing: var(--tracking-wide);line-height: 1;min-block-size: 1.9rem;padding: 0.48rem 0.7rem;text-transform: uppercase}.ui-segmented-buttons__button[aria-pressed='true'],.ui-segmented-buttons__button.is-active{background: var(--accent);border-color: var(--accent);color: var(--button-text)}.ui-segmented-buttons__button:disabled,.ui-segmented-buttons__button[aria-disabled='true']{cursor: not-allowed;opacity: 0.45}.ui-property{align-items: baseline;display: grid;gap: var(--space-sm);grid-template-columns: minmax(6rem,38%) 1fr}.ui-property__label{color: var(--text-dim);font-family: var(--mono);font-size: var(--text-2xs);letter-spacing: var(--tracking-wide);text-transform: uppercase}.ui-property__value{color: var(--text);font-size: var(--text-sm);min-inline-size: 0}.ui-pane{background: var(--panel);border: 1px solid var(--line);border-radius: var(--radius-md);display: flex;flex-direction: column;min-block-size: 0;min-inline-size: 0;overflow: hidden}.ui-pane__head{align-items: center;background: var(--bg-elevated);border-block-end: 1px solid var(--line);cursor: grab;display: flex;flex: none;gap: var(--space-2xs);min-block-size: var(--pane-head,2.5rem);padding: var(--space-2xs) var(--space-xs)}.ui-pane__head:active{cursor: grabbing}.ui-pane__title{flex: 1 1 auto;font-size: var(--text-xs);min-inline-size: 0;overflow: hidden;text-overflow: ellipsis;white-space: nowrap}.ui-pane__title-input{background: var(--bg-elevated);border: 1px solid var(--accent);border-radius: var(--radius-sm);color: inherit;font: inherit;letter-spacing: inherit;margin-block: -1px;max-inline-size: 100%;min-inline-size: 0;padding: 0 0.25rem}.ui-pane__actions{display: flex;flex: 0 1 auto;flex-wrap: nowrap;gap: var(--space-2xs);min-inline-size: 0;overflow-x: auto;scrollbar-width: thin}.ui-pane__actions:empty{display: none}.ui-pane__body{display: flex;flex: 1;flex-direction: column;min-block-size: 0;min-inline-size: 0;overflow: auto}.ui-selectionbar{align-items: center;background: var(--panel-strong);border: 1px solid var(--line-strong);border-radius: var(--radius-md);box-shadow: var(--shadow-raised);display: flex;flex-wrap: wrap;gap: var(--space-sm) var(--space-md);justify-content: space-between;padding: 0.5rem 0.85rem}.ui-selectionbar__count{color: var(--text);font-family: var(--mono);font-size: var(--text-sm)}.ui-selectionbar__actions{display: flex;flex-wrap: wrap;gap: var(--space-xs)}.ui-selectionbar--anchored,.ui-toolstrip--anchored{--anchor-offset: var(--space-sm);inset-inline-start: 50%;max-inline-size: calc(100% - 2 * var(--space-md));position: absolute;transform: translateX(-50%)}.ui-selectionbar--anchored,.ui-toolstrip--anchored,.ui-selectionbar--anchor-block-end,.ui-toolstrip--anchor-block-end{inset-block: auto max(var(--anchor-offset),var(--safe-area-bottom))}.ui-selectionbar--anchor-block-start,.ui-toolstrip--anchor-block-start{inset-block: max(var(--anchor-offset),var(--safe-area-top)) auto}@media (hover: hover){.ui-segmented-buttons__button:hover:not(:disabled,[aria-disabled='true'],[aria-pressed='true'],.is-active){background: var(--panel);border-color: var(--line-strong);color: var(--text)}}.ui-splitter{--splitter-pos: 50%;--splitter-handle: 0.75rem;--splitter-pane-min: 0px;background: var(--panel);border: 1px solid var(--line);border-radius: var(--radius-md);display: grid;grid-template-columns: minmax(var(--splitter-pane-min),var(--splitter-pos)) var(--splitter-handle) minmax(0,1fr);min-block-size: 18rem;overflow: hidden}.ui-splitter--vertical{grid-template-columns: minmax(var(--splitter-pane-min),var(--splitter-pos)) var(--splitter-handle) minmax(0,1fr)}.ui-splitter--horizontal{grid-template-columns: 1fr;grid-template-rows: minmax(var(--splitter-pane-min),var(--splitter-pos)) var(--splitter-handle) minmax(0,1fr)}.ui-splitter__pane{min-block-size: 0;min-inline-size: 0;overflow: auto;padding: var(--space-md)}.ui-splitter__handle{align-self: stretch;background: var(--panel-strong);border-block: 0;border-inline: 1px solid var(--line);cursor: col-resize;display: grid;min-inline-size: var(--splitter-handle);place-items: center;touch-action: none}.ui-splitter__handle::before{background: var(--line-strong);border-radius: var(--radius-pill);block-size: 2.5rem;content: "";inline-size: 2px}.ui-splitter__handle:hover::before,.ui-splitter__handle.is-active::before,.ui-splitter__handle[aria-valuenow]:focus-visible::before{background: var(--accent)}.ui-splitter__handle:focus-visible{outline: 2px solid var(--focus-ring);outline-offset: -2px}.ui-splitter--horizontal .ui-splitter__handle{border-block: 1px solid var(--line);border-inline: 0;cursor: row-resize;min-block-size: var(--splitter-handle);min-inline-size: 0}.ui-splitter--horizontal .ui-splitter__handle::before{block-size: 2px;inline-size: 2.5rem}@media (forced-colors: active){.ui-segmented-buttons__button[aria-pressed='true'],.ui-segmented-buttons__button.is-active{background: Highlight;border-color: Highlight;color: HighlightText}.ui-splitter__handle{border-color: CanvasText}.ui-splitter__handle::before{background: CanvasText}}}
@@ -165,13 +165,42 @@ Hard acceptance criteria for every step below:
165
165
  var(--accent) …)` declared on `:root` and only re-evaluates against the new
166
166
  accent on the element that carries it; a skin on a subtree would leave that
167
167
  family stale, so the selectors are `:root`-anchored and a subtree skin
168
- no-ops. Each skin re-points only `--accent` (+ a dark `--dotmatrix-glow`);
169
- the family + dot-matrix + glyphs follow automatically. Single-hue per skin →
168
+ no-ops. Each skin re-points `--accent` (+ a dark `--dotmatrix-glow`); the
169
+ family + dot-matrix + glyphs follow automatically. Single-hue per skin →
170
170
  keeps the one-accent discipline while unlocking range across skins. No
171
171
  multi-hue palette. (A future per-subtree skin would need to re-declare the
172
172
  derived family — deferred; whole-page is the natural colorway scope.) Skins
173
173
  inherit the existing `forced-colors` + `print` behavior in `css/base.css` /
174
- `css/forms.css` (they touch only the accent, never the canvas).
174
+ `css/forms.css`.
175
+
176
+ **Amended in 0.8.0 — a colorway also owns the neutral canvas.** The original
177
+ rule was accent-only, on the reasoning that the canvas is identity and the
178
+ accent is expression. A downstream consumer disproved it in the most direct
179
+ way available: offering these three as pickable looks, it found that
180
+ "Amber CRT" left the surface grey, and shipped a hand-written amber canvas of
181
+ its own — raw hex, dark theme only, outside OKLCH, outside `check-contrast`,
182
+ with `e-ink` silently un-tinted because a partial canvas has no failing edge.
183
+ Every one of those defects is a consequence of the rule, not of the consumer.
184
+ A look named after a phosphor display that renders on neutral grey is not a
185
+ colorway; it is an accent swatch with a colorway's name.
186
+
187
+ The amendment keeps the discipline that made the original rule right:
188
+
189
+ - The canvas is **derived, not picked**. Each of the ten neutrals
190
+ (`SKIN_CANVAS_TOKENS` in `tokens/skins.js`) keeps the core token's OKLCH
191
+ *lightness* exactly, adopts the skin accent's *hue*, and takes a small
192
+ role-scaled chroma. Contrast tracks relative luminance, which OKLCH L
193
+ tracks closely, so holding L fixed holds every pairing's ratio.
194
+ - It is **proven, not asserted**. `gen-contrast.mjs` widens a canvas-moving
195
+ skin's audit from the accent subset to the *full* 24-pair table, so all 21
196
+ gated pairings are re-measured per skin per theme on every run.
197
+ - The canvas is **all-or-nothing**, in both directions: `check-skins.mjs`
198
+ rejects a skin that re-points some neutrals but not others, and one that
199
+ re-points them in one theme but not the other. Those are exactly the two
200
+ shapes the consumer's hand-written version had.
201
+ - **Status colours stay locked.** They are not in `SKIN_CANVAS_TOKENS`: a
202
+ warning must look like a warning in every skin. Deriving status from a
203
+ colorway remains rejected (see Rejected alternatives).
175
204
  5. **OKLCH for new work first.** *(done for colorways — accents authored in
176
205
  OKLCH in `tokens/skins.js`; the contrast tooling now parses `oklch()` →
177
206
  sRGB, so skin accents are gated, not eyeballed.)* The core ramp followed in
package/docs/contrast.md CHANGED
@@ -111,36 +111,73 @@ palette untouched). Accents are authored in OKLCH; `--accent-text` is the
111
111
 
112
112
  | Foreground | Background | Role | Held to | Ratio | APCA _(advisory)_ | Verdict |
113
113
  | --- | --- | --- | --- | --- | --- | --- |
114
- | `--accent-text` | `--bg` | Accent text on page background | AA text (4.5:1) | 6.75:1 | Lc 78.9 | ✅ pass |
114
+ | `--text` | `--bg` | Body text on page background | AA text (4.5:1) | 17.97:1 | Lc 99.0 | ✅ pass |
115
+ | `--text` | `--surface` | Body text on a card/panel | AA text (4.5:1) | 19.84:1 | Lc 105.8 | ✅ pass |
116
+ | `--text` | `--surface-muted` | Body text on a muted panel | AA text (4.5:1) | 16.77:1 | Lc 94.4 | ✅ pass |
117
+ | `--text-soft` | `--bg` | Secondary text on page background | AA text (4.5:1) | 11.20:1 | Lc 91.3 | ✅ pass |
118
+ | `--text-soft` | `--surface` | Secondary text on a card | AA text (4.5:1) | 12.37:1 | Lc 98.1 | ✅ pass |
119
+ | `--text-soft` | `--surface-muted` | Secondary text on a muted panel | AA text (4.5:1) | 10.45:1 | Lc 86.8 | ✅ pass |
120
+ | `--text-dim` | `--bg` | Dim/meta text on page background | AA text (4.5:1) | 5.11:1 | Lc 71.4 | ✅ pass |
121
+ | `--text-dim` | `--surface` | Dim/meta text on a card | AA text (4.5:1) | 5.64:1 | Lc 78.2 | ✅ pass |
122
+ | `--text-dim` | `--surface-muted` | Dim/meta text on a muted panel | AA text (4.5:1) | 4.77:1 | Lc 66.9 | ✅ pass |
123
+ | `--accent-text` | `--bg` | Accent text on page background | AA text (4.5:1) | 6.73:1 | Lc 78.7 | ✅ pass |
115
124
  | `--accent-text` | `--surface` | Accent text on a card | AA text (4.5:1) | 7.44:1 | Lc 85.6 | ✅ pass |
116
- | `--text-soft` | `--accent-soft` over `--surface-muted` | Neutral tag/badge text on an accent tint | AA text (4.5:1) | 9.13:1 | Lc 78.7 | ✅ pass |
125
+ | `--text-soft` | `--accent-soft` over `--surface-muted` | Neutral tag/badge text on an accent tint | AA text (4.5:1) | 9.19:1 | Lc 78.7 | ✅ pass |
117
126
  | `--accent-text` | `--accent-soft` | Accent text on an accent tint | Advisory (translucent tint — not gated) | 6.48:1 | Lc 76.1 | ℹ️ not gated |
118
127
  | `--accent-text` | `--bg-accent` | Accent text on an accent-tinted surface | Advisory (translucent tint — not gated) | 6.85:1 | Lc 79.9 | ℹ️ not gated |
119
128
  | `--button-text` | `--accent` | Label on the primary button | AA text (4.5:1) | 5.66:1 | Lc 83.3 | ✅ pass |
120
129
  | `--on-accent` | `--accent` | Ink on an accent fill | AA text (4.5:1) | 5.66:1 | Lc 83.3 | ✅ pass |
121
- | `--focus-ring` | `--bg` | Focus ring vs page background | UI / large (3:1) | 5.14:1 | Lc 71.4 | ✅ pass |
130
+ | `--focus-ring` | `--bg` | Focus ring vs page background | UI / large (3:1) | 5.13:1 | Lc 71.2 | ✅ pass |
122
131
  | `--focus-ring` | `--surface` | Focus ring vs a card | UI / large (3:1) | 5.66:1 | Lc 78.0 | ✅ pass |
123
- | `--accent` | `--bg` | Accent fill vs page background | UI / large (3:1) | 5.14:1 | Lc 71.4 | ✅ pass |
132
+ | `--accent` | `--bg` | Accent fill vs page background | UI / large (3:1) | 5.13:1 | Lc 71.2 | ✅ pass |
133
+ | `--success` | `--surface` | Success indicator vs a card | UI / large (3:1) | 5.04:1 | Lc 74.6 | ✅ pass |
134
+ | `--warning` | `--surface` | Warning indicator vs a card | UI / large (3:1) | 5.60:1 | Lc 77.8 | ✅ pass |
135
+ | `--danger` | `--surface` | Danger indicator vs a card | UI / large (3:1) | 6.21:1 | Lc 78.9 | ✅ pass |
136
+ | `--info` | `--surface` | Info indicator vs a card | UI / large (3:1) | 5.77:1 | Lc 78.3 | ✅ pass |
137
+ | `--line-strong` | `--surface` | Strong hairline vs a card | Decorative (1.4.11-exempt) | 2.41:1 | Lc 47.4 | ℹ️ not gated |
124
138
 
125
139
  ### Amber CRT — dark
126
140
 
127
141
  | Foreground | Background | Role | Held to | Ratio | APCA _(advisory)_ | Verdict |
128
142
  | --- | --- | --- | --- | --- | --- | --- |
143
+ | `--text` | `--bg` | Body text on page background | AA text (4.5:1) | 15.01:1 | Lc 91.0 | ✅ pass |
144
+ | `--text` | `--surface` | Body text on a card/panel | AA text (4.5:1) | 13.64:1 | Lc 90.0 | ✅ pass |
145
+ | `--text` | `--surface-muted` | Body text on a muted panel | AA text (4.5:1) | 12.43:1 | Lc 88.9 | ✅ pass |
146
+ | `--text-soft` | `--bg` | Secondary text on page background | AA text (4.5:1) | 11.19:1 | Lc 72.7 | ✅ pass |
147
+ | `--text-soft` | `--surface` | Secondary text on a card | AA text (4.5:1) | 10.18:1 | Lc 71.7 | ✅ pass |
148
+ | `--text-soft` | `--surface-muted` | Secondary text on a muted panel | AA text (4.5:1) | 9.27:1 | Lc 70.5 | ✅ pass |
149
+ | `--text-dim` | `--bg` | Dim/meta text on page background | AA text (4.5:1) | 7.16:1 | Lc 50.2 | ✅ pass |
150
+ | `--text-dim` | `--surface` | Dim/meta text on a card | AA text (4.5:1) | 6.51:1 | Lc 49.2 | ✅ pass |
151
+ | `--text-dim` | `--surface-muted` | Dim/meta text on a muted panel | AA text (4.5:1) | 5.93:1 | Lc 48.1 | ✅ pass |
129
152
  | `--accent-text` | `--bg` | Accent text on page background | AA text (4.5:1) | 11.84:1 | Lc 76.1 | ✅ pass |
130
153
  | `--accent-text` | `--surface` | Accent text on a card | AA text (4.5:1) | 10.77:1 | Lc 75.1 | ✅ pass |
131
- | `--text-soft` | `--accent-soft` over `--surface-muted` | Neutral tag/badge text on an accent tint | AA text (4.5:1) | 6.83:1 | Lc 65.5 | ✅ pass |
154
+ | `--text-soft` | `--accent-soft` over `--surface-muted` | Neutral tag/badge text on an accent tint | AA text (4.5:1) | 6.82:1 | Lc 65.4 | ✅ pass |
132
155
  | `--accent-text` | `--accent-soft` | Accent text on an accent tint | Advisory (translucent tint — not gated) | 1.46:1 | Lc 20.8 | ℹ️ not gated |
133
156
  | `--accent-text` | `--bg-accent` | Accent text on an accent-tinted surface | Advisory (translucent tint — not gated) | 1.51:1 | Lc 23.2 | ℹ️ not gated |
134
157
  | `--button-text` | `--accent` | Label on the primary button | AA text (4.5:1) | 11.88:1 | Lc 71.6 | ✅ pass |
135
158
  | `--on-accent` | `--accent` | Ink on an accent fill | AA text (4.5:1) | 11.88:1 | Lc 71.6 | ✅ pass |
136
- | `--focus-ring` | `--bg` | Focus ring vs page background | UI / large (3:1) | 10.60:1 | Lc 69.9 | ✅ pass |
159
+ | `--focus-ring` | `--bg` | Focus ring vs page background | UI / large (3:1) | 10.60:1 | Lc 69.8 | ✅ pass |
137
160
  | `--focus-ring` | `--surface` | Focus ring vs a card | UI / large (3:1) | 9.64:1 | Lc 68.9 | ✅ pass |
138
- | `--accent` | `--bg` | Accent fill vs page background | UI / large (3:1) | 10.60:1 | Lc 69.9 | ✅ pass |
161
+ | `--accent` | `--bg` | Accent fill vs page background | UI / large (3:1) | 10.60:1 | Lc 69.8 | ✅ pass |
162
+ | `--success` | `--surface` | Success indicator vs a card | UI / large (3:1) | 7.58:1 | Lc 56.6 | ✅ pass |
163
+ | `--warning` | `--surface` | Warning indicator vs a card | UI / large (3:1) | 9.29:1 | Lc 66.6 | ✅ pass |
164
+ | `--danger` | `--surface` | Danger indicator vs a card | UI / large (3:1) | 5.23:1 | Lc 41.5 | ✅ pass |
165
+ | `--info` | `--surface` | Info indicator vs a card | UI / large (3:1) | 7.33:1 | Lc 54.8 | ✅ pass |
166
+ | `--line-strong` | `--surface` | Strong hairline vs a card | Decorative (1.4.11-exempt) | 2.28:1 | Lc 14.5 | ℹ️ not gated |
139
167
 
140
168
  ### E-ink — light
141
169
 
142
170
  | Foreground | Background | Role | Held to | Ratio | APCA _(advisory)_ | Verdict |
143
171
  | --- | --- | --- | --- | --- | --- | --- |
172
+ | `--text` | `--bg` | Body text on page background | AA text (4.5:1) | 17.98:1 | Lc 99.2 | ✅ pass |
173
+ | `--text` | `--surface` | Body text on a card/panel | AA text (4.5:1) | 19.79:1 | Lc 105.8 | ✅ pass |
174
+ | `--text` | `--surface-muted` | Body text on a muted panel | AA text (4.5:1) | 16.73:1 | Lc 94.5 | ✅ pass |
175
+ | `--text-soft` | `--bg` | Secondary text on page background | AA text (4.5:1) | 11.16:1 | Lc 91.4 | ✅ pass |
176
+ | `--text-soft` | `--surface` | Secondary text on a card | AA text (4.5:1) | 12.29:1 | Lc 98.0 | ✅ pass |
177
+ | `--text-soft` | `--surface-muted` | Secondary text on a muted panel | AA text (4.5:1) | 10.39:1 | Lc 86.7 | ✅ pass |
178
+ | `--text-dim` | `--bg` | Dim/meta text on page background | AA text (4.5:1) | 5.09:1 | Lc 71.4 | ✅ pass |
179
+ | `--text-dim` | `--surface` | Dim/meta text on a card | AA text (4.5:1) | 5.61:1 | Lc 78.0 | ✅ pass |
180
+ | `--text-dim` | `--surface-muted` | Dim/meta text on a muted panel | AA text (4.5:1) | 4.74:1 | Lc 66.7 | ✅ pass |
144
181
  | `--accent-text` | `--bg` | Accent text on page background | AA text (4.5:1) | 12.23:1 | Lc 93.3 | ✅ pass |
145
182
  | `--accent-text` | `--surface` | Accent text on a card | AA text (4.5:1) | 13.47:1 | Lc 99.9 | ✅ pass |
146
183
  | `--text-soft` | `--accent-soft` over `--surface-muted` | Neutral tag/badge text on an accent tint | AA text (4.5:1) | 8.78:1 | Lc 76.3 | ✅ pass |
@@ -151,51 +188,98 @@ palette untouched). Accents are authored in OKLCH; `--accent-text` is the
151
188
  | `--focus-ring` | `--bg` | Focus ring vs page background | UI / large (3:1) | 10.66:1 | Lc 90.4 | ✅ pass |
152
189
  | `--focus-ring` | `--surface` | Focus ring vs a card | UI / large (3:1) | 11.74:1 | Lc 97.0 | ✅ pass |
153
190
  | `--accent` | `--bg` | Accent fill vs page background | UI / large (3:1) | 10.66:1 | Lc 90.4 | ✅ pass |
191
+ | `--success` | `--surface` | Success indicator vs a card | UI / large (3:1) | 5.04:1 | Lc 74.6 | ✅ pass |
192
+ | `--warning` | `--surface` | Warning indicator vs a card | UI / large (3:1) | 5.60:1 | Lc 77.8 | ✅ pass |
193
+ | `--danger` | `--surface` | Danger indicator vs a card | UI / large (3:1) | 6.21:1 | Lc 78.9 | ✅ pass |
194
+ | `--info` | `--surface` | Info indicator vs a card | UI / large (3:1) | 5.77:1 | Lc 78.3 | ✅ pass |
195
+ | `--line-strong` | `--surface` | Strong hairline vs a card | Decorative (1.4.11-exempt) | 2.39:1 | Lc 47.1 | ℹ️ not gated |
154
196
 
155
197
  ### E-ink — dark
156
198
 
157
199
  | Foreground | Background | Role | Held to | Ratio | APCA _(advisory)_ | Verdict |
158
200
  | --- | --- | --- | --- | --- | --- | --- |
201
+ | `--text` | `--bg` | Body text on page background | AA text (4.5:1) | 15.02:1 | Lc 91.1 | ✅ pass |
202
+ | `--text` | `--surface` | Body text on a card/panel | AA text (4.5:1) | 13.66:1 | Lc 90.1 | ✅ pass |
203
+ | `--text` | `--surface-muted` | Body text on a muted panel | AA text (4.5:1) | 12.45:1 | Lc 89.0 | ✅ pass |
204
+ | `--text-soft` | `--bg` | Secondary text on page background | AA text (4.5:1) | 11.20:1 | Lc 72.8 | ✅ pass |
205
+ | `--text-soft` | `--surface` | Secondary text on a card | AA text (4.5:1) | 10.19:1 | Lc 71.8 | ✅ pass |
206
+ | `--text-soft` | `--surface-muted` | Secondary text on a muted panel | AA text (4.5:1) | 9.29:1 | Lc 70.6 | ✅ pass |
207
+ | `--text-dim` | `--bg` | Dim/meta text on page background | AA text (4.5:1) | 7.17:1 | Lc 50.3 | ✅ pass |
208
+ | `--text-dim` | `--surface` | Dim/meta text on a card | AA text (4.5:1) | 6.52:1 | Lc 49.3 | ✅ pass |
209
+ | `--text-dim` | `--surface-muted` | Dim/meta text on a muted panel | AA text (4.5:1) | 5.94:1 | Lc 48.2 | ✅ pass |
159
210
  | `--accent-text` | `--bg` | Accent text on page background | AA text (4.5:1) | 12.72:1 | Lc 80.3 | ✅ pass |
160
211
  | `--accent-text` | `--surface` | Accent text on a card | AA text (4.5:1) | 11.57:1 | Lc 79.3 | ✅ pass |
161
- | `--text-soft` | `--accent-soft` over `--surface-muted` | Neutral tag/badge text on an accent tint | AA text (4.5:1) | 6.66:1 | Lc 65.0 | ✅ pass |
212
+ | `--text-soft` | `--accent-soft` over `--surface-muted` | Neutral tag/badge text on an accent tint | AA text (4.5:1) | 6.67:1 | Lc 65.0 | ✅ pass |
162
213
  | `--accent-text` | `--accent-soft` | Accent text on an accent tint | Advisory (translucent tint — not gated) | 1.38:1 | Lc 18.0 | ℹ️ not gated |
163
214
  | `--accent-text` | `--bg-accent` | Accent text on an accent-tinted surface | Advisory (translucent tint — not gated) | 1.42:1 | Lc 19.9 | ℹ️ not gated |
164
215
  | `--button-text` | `--accent` | Label on the primary button | AA text (4.5:1) | 12.86:1 | Lc 75.6 | ✅ pass |
165
216
  | `--on-accent` | `--accent` | Ink on an accent fill | AA text (4.5:1) | 12.86:1 | Lc 75.6 | ✅ pass |
166
- | `--focus-ring` | `--bg` | Focus ring vs page background | UI / large (3:1) | 11.48:1 | Lc 74.1 | ✅ pass |
217
+ | `--focus-ring` | `--bg` | Focus ring vs page background | UI / large (3:1) | 11.47:1 | Lc 74.1 | ✅ pass |
167
218
  | `--focus-ring` | `--surface` | Focus ring vs a card | UI / large (3:1) | 10.44:1 | Lc 73.1 | ✅ pass |
168
- | `--accent` | `--bg` | Accent fill vs page background | UI / large (3:1) | 11.48:1 | Lc 74.1 | ✅ pass |
219
+ | `--accent` | `--bg` | Accent fill vs page background | UI / large (3:1) | 11.47:1 | Lc 74.1 | ✅ pass |
220
+ | `--success` | `--surface` | Success indicator vs a card | UI / large (3:1) | 7.58:1 | Lc 56.6 | ✅ pass |
221
+ | `--warning` | `--surface` | Warning indicator vs a card | UI / large (3:1) | 9.29:1 | Lc 66.6 | ✅ pass |
222
+ | `--danger` | `--surface` | Danger indicator vs a card | UI / large (3:1) | 5.23:1 | Lc 41.5 | ✅ pass |
223
+ | `--info` | `--surface` | Info indicator vs a card | UI / large (3:1) | 7.33:1 | Lc 54.8 | ✅ pass |
224
+ | `--line-strong` | `--surface` | Strong hairline vs a card | Decorative (1.4.11-exempt) | 2.29:1 | Lc 14.6 | ℹ️ not gated |
169
225
 
170
226
  ### Phosphor Green — light
171
227
 
172
228
  | Foreground | Background | Role | Held to | Ratio | APCA _(advisory)_ | Verdict |
173
229
  | --- | --- | --- | --- | --- | --- | --- |
174
- | `--accent-text` | `--bg` | Accent text on page background | AA text (4.5:1) | 6.22:1 | Lc 76.6 | ✅ pass |
230
+ | `--text` | `--bg` | Body text on page background | AA text (4.5:1) | 18.02:1 | Lc 99.5 | ✅ pass |
231
+ | `--text` | `--surface` | Body text on a card/panel | AA text (4.5:1) | 19.72:1 | Lc 105.8 | ✅ pass |
232
+ | `--text` | `--surface-muted` | Body text on a muted panel | AA text (4.5:1) | 16.67:1 | Lc 94.4 | ✅ pass |
233
+ | `--text-soft` | `--bg` | Secondary text on page background | AA text (4.5:1) | 11.10:1 | Lc 91.5 | ✅ pass |
234
+ | `--text-soft` | `--surface` | Secondary text on a card | AA text (4.5:1) | 12.14:1 | Lc 97.7 | ✅ pass |
235
+ | `--text-soft` | `--surface-muted` | Secondary text on a muted panel | AA text (4.5:1) | 10.26:1 | Lc 86.4 | ✅ pass |
236
+ | `--text-dim` | `--bg` | Dim/meta text on page background | AA text (4.5:1) | 5.06:1 | Lc 71.4 | ✅ pass |
237
+ | `--text-dim` | `--surface` | Dim/meta text on a card | AA text (4.5:1) | 5.53:1 | Lc 77.7 | ✅ pass |
238
+ | `--text-dim` | `--surface-muted` | Dim/meta text on a muted panel | AA text (4.5:1) | 4.68:1 | Lc 66.3 | ✅ pass |
239
+ | `--accent-text` | `--bg` | Accent text on page background | AA text (4.5:1) | 6.26:1 | Lc 77.1 | ✅ pass |
175
240
  | `--accent-text` | `--surface` | Accent text on a card | AA text (4.5:1) | 6.85:1 | Lc 83.3 | ✅ pass |
176
- | `--text-soft` | `--accent-soft` over `--surface-muted` | Neutral tag/badge text on an accent tint | AA text (4.5:1) | 9.16:1 | Lc 78.9 | ✅ pass |
241
+ | `--text-soft` | `--accent-soft` over `--surface-muted` | Neutral tag/badge text on an accent tint | AA text (4.5:1) | 9.05:1 | Lc 78.5 | ✅ pass |
177
242
  | `--accent-text` | `--accent-soft` | Accent text on an accent tint | Advisory (translucent tint — not gated) | 5.98:1 | Lc 74.1 | ℹ️ not gated |
178
243
  | `--accent-text` | `--bg-accent` | Accent text on an accent-tinted surface | Advisory (translucent tint — not gated) | 6.32:1 | Lc 77.7 | ℹ️ not gated |
179
244
  | `--button-text` | `--accent` | Label on the primary button | AA text (4.5:1) | 5.19:1 | Lc 80.7 | ✅ pass |
180
245
  | `--on-accent` | `--accent` | Ink on an accent fill | AA text (4.5:1) | 5.19:1 | Lc 80.7 | ✅ pass |
181
- | `--focus-ring` | `--bg` | Focus ring vs page background | UI / large (3:1) | 4.71:1 | Lc 68.6 | ✅ pass |
246
+ | `--focus-ring` | `--bg` | Focus ring vs page background | UI / large (3:1) | 4.74:1 | Lc 69.1 | ✅ pass |
182
247
  | `--focus-ring` | `--surface` | Focus ring vs a card | UI / large (3:1) | 5.19:1 | Lc 75.3 | ✅ pass |
183
- | `--accent` | `--bg` | Accent fill vs page background | UI / large (3:1) | 4.71:1 | Lc 68.6 | ✅ pass |
248
+ | `--accent` | `--bg` | Accent fill vs page background | UI / large (3:1) | 4.74:1 | Lc 69.1 | ✅ pass |
249
+ | `--success` | `--surface` | Success indicator vs a card | UI / large (3:1) | 5.04:1 | Lc 74.6 | ✅ pass |
250
+ | `--warning` | `--surface` | Warning indicator vs a card | UI / large (3:1) | 5.60:1 | Lc 77.8 | ✅ pass |
251
+ | `--danger` | `--surface` | Danger indicator vs a card | UI / large (3:1) | 6.21:1 | Lc 78.9 | ✅ pass |
252
+ | `--info` | `--surface` | Info indicator vs a card | UI / large (3:1) | 5.77:1 | Lc 78.3 | ✅ pass |
253
+ | `--line-strong` | `--surface` | Strong hairline vs a card | Decorative (1.4.11-exempt) | 2.35:1 | Lc 46.4 | ℹ️ not gated |
184
254
 
185
255
  ### Phosphor Green — dark
186
256
 
187
257
  | Foreground | Background | Role | Held to | Ratio | APCA _(advisory)_ | Verdict |
188
258
  | --- | --- | --- | --- | --- | --- | --- |
189
- | `--accent-text` | `--bg` | Accent text on page background | AA text (4.5:1) | 13.18:1 | Lc 82.8 | ✅ pass |
259
+ | `--text` | `--bg` | Body text on page background | AA text (4.5:1) | 15.11:1 | Lc 91.8 | ✅ pass |
260
+ | `--text` | `--surface` | Body text on a card/panel | AA text (4.5:1) | 13.80:1 | Lc 90.8 | ✅ pass |
261
+ | `--text` | `--surface-muted` | Body text on a muted panel | AA text (4.5:1) | 12.57:1 | Lc 89.6 | ✅ pass |
262
+ | `--text-soft` | `--bg` | Secondary text on page background | AA text (4.5:1) | 11.28:1 | Lc 73.3 | ✅ pass |
263
+ | `--text-soft` | `--surface` | Secondary text on a card | AA text (4.5:1) | 10.30:1 | Lc 72.4 | ✅ pass |
264
+ | `--text-soft` | `--surface-muted` | Secondary text on a muted panel | AA text (4.5:1) | 9.38:1 | Lc 71.2 | ✅ pass |
265
+ | `--text-dim` | `--bg` | Dim/meta text on page background | AA text (4.5:1) | 7.22:1 | Lc 50.8 | ✅ pass |
266
+ | `--text-dim` | `--surface` | Dim/meta text on a card | AA text (4.5:1) | 6.60:1 | Lc 49.8 | ✅ pass |
267
+ | `--text-dim` | `--surface-muted` | Dim/meta text on a muted panel | AA text (4.5:1) | 6.01:1 | Lc 48.7 | ✅ pass |
268
+ | `--accent-text` | `--bg` | Accent text on page background | AA text (4.5:1) | 13.13:1 | Lc 82.8 | ✅ pass |
190
269
  | `--accent-text` | `--surface` | Accent text on a card | AA text (4.5:1) | 11.99:1 | Lc 81.8 | ✅ pass |
191
- | `--text-soft` | `--accent-soft` over `--surface-muted` | Neutral tag/badge text on an accent tint | AA text (4.5:1) | 6.66:1 | Lc 65.0 | ✅ pass |
270
+ | `--text-soft` | `--accent-soft` over `--surface-muted` | Neutral tag/badge text on an accent tint | AA text (4.5:1) | 6.74:1 | Lc 65.6 | ✅ pass |
192
271
  | `--accent-text` | `--accent-soft` | Accent text on an accent tint | Advisory (translucent tint — not gated) | 1.33:1 | Lc 15.3 | ℹ️ not gated |
193
272
  | `--accent-text` | `--bg-accent` | Accent text on an accent-tinted surface | Advisory (translucent tint — not gated) | 1.37:1 | Lc 17.3 | ℹ️ not gated |
194
273
  | `--button-text` | `--accent` | Label on the primary button | AA text (4.5:1) | 13.75:1 | Lc 79.7 | ✅ pass |
195
274
  | `--on-accent` | `--accent` | Ink on an accent fill | AA text (4.5:1) | 13.75:1 | Lc 79.7 | ✅ pass |
196
- | `--focus-ring` | `--bg` | Focus ring vs page background | UI / large (3:1) | 12.27:1 | Lc 78.5 | ✅ pass |
275
+ | `--focus-ring` | `--bg` | Focus ring vs page background | UI / large (3:1) | 12.22:1 | Lc 78.5 | ✅ pass |
197
276
  | `--focus-ring` | `--surface` | Focus ring vs a card | UI / large (3:1) | 11.16:1 | Lc 77.5 | ✅ pass |
198
- | `--accent` | `--bg` | Accent fill vs page background | UI / large (3:1) | 12.27:1 | Lc 78.5 | ✅ pass |
277
+ | `--accent` | `--bg` | Accent fill vs page background | UI / large (3:1) | 12.22:1 | Lc 78.5 | ✅ pass |
278
+ | `--success` | `--surface` | Success indicator vs a card | UI / large (3:1) | 7.58:1 | Lc 56.6 | ✅ pass |
279
+ | `--warning` | `--surface` | Warning indicator vs a card | UI / large (3:1) | 9.29:1 | Lc 66.6 | ✅ pass |
280
+ | `--danger` | `--surface` | Danger indicator vs a card | UI / large (3:1) | 5.23:1 | Lc 41.5 | ✅ pass |
281
+ | `--info` | `--surface` | Info indicator vs a card | UI / large (3:1) | 7.33:1 | Lc 54.8 | ✅ pass |
282
+ | `--line-strong` | `--surface` | Strong hairline vs a card | Decorative (1.4.11-exempt) | 2.33:1 | Lc 15.1 | ℹ️ not gated |
199
283
 
200
284
  ## Data-viz palette (advisory)
201
285
 
package/docs/reference.md CHANGED
@@ -9,7 +9,7 @@ rendering of every class is the kitchen-sink demo:
9
9
  **<https://ponchia.github.io/bronto-ui/>**. Theming knobs and the token
10
10
  contract: [docs/theming.md](theming.md).
11
11
 
12
- - 644 classes across 179 component groups
12
+ - 669 classes across 183 component groups
13
13
  - Import the typed registry: `import { cls, ui, cx } from '@ponchia/ui/classes'`
14
14
  - Validate markup as data (no JS/TS): `@ponchia/ui/classes.json` — the same
15
15
  vocabulary as language-neutral JSON (`groups`, `classes`, `states`,
@@ -268,7 +268,9 @@ each one matches a real selector in the stylesheet.
268
268
  | Registry key | Class | Kind |
269
269
  | --- | --- | --- |
270
270
  | `cls.button` | `ui-button` | base |
271
+ | `cls.buttonLabel` | `ui-button__label` | part |
271
272
  | `cls.buttonDanger` | `ui-button--danger` | modifier |
273
+ | `cls.buttonDense` | `ui-button--dense` | modifier |
272
274
  | `cls.buttonGhost` | `ui-button--ghost` | modifier |
273
275
  | `cls.buttonIcon` | `ui-button--icon` | modifier |
274
276
  | `cls.buttonLg` | `ui-button--lg` | modifier |
@@ -577,6 +579,10 @@ each one matches a real selector in the stylesheet.
577
579
  | Registry key | Class | Kind |
578
580
  | --- | --- | --- |
579
581
  | `cls.emptyState` | `ui-empty-state` | base |
582
+ | `cls.emptyStateGlyph` | `ui-empty-state__glyph` | part |
583
+ | `cls.emptyStateHint` | `ui-empty-state__hint` | part |
584
+ | `cls.emptyStateLead` | `ui-empty-state__lead` | part |
585
+ | `cls.emptyStateInvite` | `ui-empty-state--invite` | modifier |
580
586
 
581
587
  ### `.ui-error-summary`
582
588
 
@@ -952,6 +958,17 @@ each one matches a real selector in the stylesheet.
952
958
  | `cls.pagination` | `ui-pagination` | base |
953
959
  | `cls.paginationItem` | `ui-pagination__item` | part |
954
960
 
961
+ ### `.ui-pane`
962
+
963
+ | Registry key | Class | Kind |
964
+ | --- | --- | --- |
965
+ | `cls.pane` | `ui-pane` | base |
966
+ | `cls.paneActions` | `ui-pane__actions` | part |
967
+ | `cls.paneBody` | `ui-pane__body` | part |
968
+ | `cls.paneHead` | `ui-pane__head` | part |
969
+ | `cls.paneTitle` | `ui-pane__title` | part |
970
+ | `cls.paneTitleInput` | `ui-pane__title-input` | part |
971
+
955
972
  ### `.ui-panel`
956
973
 
957
974
  | Registry key | Class | Kind |
@@ -1160,6 +1177,29 @@ each one matches a real selector in the stylesheet.
1160
1177
  | `cls.selectionbar` | `ui-selectionbar` | base |
1161
1178
  | `cls.selectionbarActions` | `ui-selectionbar__actions` | part |
1162
1179
  | `cls.selectionbarCount` | `ui-selectionbar__count` | part |
1180
+ | `cls.selectionbarAnchorBlockEnd` | `ui-selectionbar--anchor-block-end` | modifier |
1181
+ | `cls.selectionbarAnchorBlockStart` | `ui-selectionbar--anchor-block-start` | modifier |
1182
+ | `cls.selectionbarAnchored` | `ui-selectionbar--anchored` | modifier |
1183
+
1184
+ ### `.ui-severity`
1185
+
1186
+ | Registry key | Class | Kind |
1187
+ | --- | --- | --- |
1188
+ | `cls.severity` | `ui-severity` | base |
1189
+
1190
+ ### `.ui-severity-dot`
1191
+
1192
+ | Registry key | Class | Kind |
1193
+ | --- | --- | --- |
1194
+ | `cls.severityDot` | `ui-severity-dot` | base |
1195
+
1196
+ ### `.ui-severity-row`
1197
+
1198
+ | Registry key | Class | Kind |
1199
+ | --- | --- | --- |
1200
+ | `cls.severityRow` | `ui-severity-row` | base |
1201
+ | `cls.severityRowMeta` | `ui-severity-row__meta` | part |
1202
+ | `cls.severityRowTitle` | `ui-severity-row__title` | part |
1163
1203
 
1164
1204
  ### `.ui-shortcut`
1165
1205
 
@@ -1511,10 +1551,15 @@ each one matches a real selector in the stylesheet.
1511
1551
  | `cls.toolstripActions` | `ui-toolstrip__actions` | part |
1512
1552
  | `cls.toolstripBrand` | `ui-toolstrip__brand` | part |
1513
1553
  | `cls.toolstripContext` | `ui-toolstrip__context` | part |
1554
+ | `cls.toolstripFill` | `ui-toolstrip__fill` | part |
1514
1555
  | `cls.toolstripGroup` | `ui-toolstrip__group` | part |
1515
1556
  | `cls.toolstripSearch` | `ui-toolstrip__search` | part |
1557
+ | `cls.toolstripAnchorBlockEnd` | `ui-toolstrip--anchor-block-end` | modifier |
1558
+ | `cls.toolstripAnchorBlockStart` | `ui-toolstrip--anchor-block-start` | modifier |
1559
+ | `cls.toolstripAnchored` | `ui-toolstrip--anchored` | modifier |
1516
1560
  | `cls.toolstripCompact` | `ui-toolstrip--compact` | modifier |
1517
1561
  | `cls.toolstripFloating` | `ui-toolstrip--floating` | modifier |
1562
+ | `cls.toolstripPane` | `ui-toolstrip--pane` | modifier |
1518
1563
 
1519
1564
  ### `.ui-tooltip`
1520
1565
 
@@ -1672,6 +1717,12 @@ Exact mirror of the `:root` blocks in `css/tokens.css`
1672
1717
  | `--space-lg` | `1.35rem` |
1673
1718
  | `--space-xl` | `1.75rem` |
1674
1719
  | `--space-2xl` | `2.5rem` |
1720
+ | `--tap-target` | `max(44px, 2.9rem)` |
1721
+ | `--tap-target-min` | `max(24px, 1.6rem)` |
1722
+ | `--safe-area-top` | `env(safe-area-inset-top, 0px)` |
1723
+ | `--safe-area-right` | `env(safe-area-inset-right, 0px)` |
1724
+ | `--safe-area-bottom` | `env(safe-area-inset-bottom, 0px)` |
1725
+ | `--safe-area-left` | `env(safe-area-inset-left, 0px)` |
1675
1726
  | `--mono` | `'JetBrains Mono', 'Fira Code', 'Cascadia Code', 'SF Mono', ui-monospace, monospace` |
1676
1727
  | `--sans` | `'Inter', 'SF Pro Text', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif` |
1677
1728
  | `--dot-font` | `'Doto', var(--mono)` |
package/docs/reporting.md CHANGED
@@ -54,18 +54,18 @@ No install? Link the same files from a CDN. Pin the version — pre-1.0, breakin
54
54
  changes ship in the minor (see [stability.md](./stability.md)):
55
55
 
56
56
  ```html
57
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@ponchia/ui@0.7.0/dist/bronto.css" />
58
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@ponchia/ui@0.7.0/dist/css/report-kit.css" />
57
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@ponchia/ui@0.8.0/dist/bronto.css" />
58
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@ponchia/ui@0.8.0/dist/css/report-kit.css" />
59
59
  ```
60
60
 
61
61
  Leaf-by-leaf CDN imports use the same `dist/css/` paths:
62
62
 
63
63
  ```html
64
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@ponchia/ui@0.7.0/dist/bronto.css" />
65
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@ponchia/ui@0.7.0/dist/css/report.css" />
66
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@ponchia/ui@0.7.0/dist/css/dataviz.css" />
67
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@ponchia/ui@0.7.0/dist/css/annotations.css" />
68
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@ponchia/ui@0.7.0/dist/css/legend.css" />
64
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@ponchia/ui@0.8.0/dist/bronto.css" />
65
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@ponchia/ui@0.8.0/dist/css/report.css" />
66
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@ponchia/ui@0.8.0/dist/css/dataviz.css" />
67
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@ponchia/ui@0.8.0/dist/css/annotations.css" />
68
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@ponchia/ui@0.8.0/dist/css/legend.css" />
69
69
  ```
70
70
 
71
71
  The CDN serves the package's own `fonts/` next to the CSS, so font URLs resolve
@@ -879,7 +879,7 @@ or validation runtime.
879
879
 
880
880
  ```json
881
881
  {
882
- "$schema": "https://cdn.jsdelivr.net/npm/@ponchia/ui@0.7.0/schemas/report-claims.v1.schema.json",
882
+ "$schema": "https://cdn.jsdelivr.net/npm/@ponchia/ui@0.8.0/schemas/report-claims.v1.schema.json",
883
883
  "schemaVersion": "bronto-report-claims.v1",
884
884
  "report": { "title": "Decision readiness", "type": "decision" },
885
885
  "claims": [
package/docs/stability.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # Public API stability
2
2
 
3
3
  `@ponchia/ui` is pre-1.0. Breaking changes ship in the minor (`0.x.0`), and
4
- patches are non-breaking. In practical terms: **PATCH releases (`0.7.x`) are
4
+ patches are non-breaking. In practical terms: **PATCH releases (`0.8.x`) are
5
5
  non-breaking bug-fixes and additive changes — safe to upgrade without review;
6
6
  MINOR releases (`0.x.0`) may include breaking changes and consumers should
7
7
  review the CHANGELOG before upgrading.** Pin `~0.x` (tilde) to accept only
@@ -68,13 +68,41 @@ Package-proven is necessary but does not establish demand. During the catalog
68
68
  freeze, do not expand package-only surfaces. Recheck this table against real
69
69
  consumer upgrades before the 1.0 release candidate.
70
70
 
71
+ > **The 0.8.0 consumer audit — read this before trusting the table below.** A
72
+ > full-surface audit of the largest downstream consumer (a Yjs-collaborative
73
+ > spatial canvas workspace: React, Vite, ~10k lines of its own CSS) found it
74
+ > using **26 of the 646 published classes**, and hand-rebuilding much of the
75
+ > rest — 10 bespoke empty states, 15 error surfaces, 17 control bars, three
76
+ > parallel severity vocabularies, its own Markdown prose layer, its own diff
77
+ > rows, its own meters, and two copies of visually-hidden text. Thirteen
78
+ > primitives that map almost exactly onto what it built return **zero** uses:
79
+ > `ui-state`, `ui-prose`, `ui-diff`, `ui-code`, `ui-meter`, `ui-progress`,
80
+ > `ui-toolstrip`, `ui-dot`, `ui-chip`, `ui-tag`, `ui-timeline`, `ui-steps`,
81
+ > `ui-job`.
82
+ >
83
+ > The lesson for this document is not about that consumer. It is that **"no
84
+ > inspected consumer imports the surface" has been measuring discoverability,
85
+ > not demand.** Those surfaces were not rejected; they were never found. The
86
+ > opt-in leaf model is right, but nothing tells a consumer which leaf it is
87
+ > about to reimplement — and `bronto-ui-check`, which catches part of it, was
88
+ > installed in that consumer and had never been run. Before any 1.0 decision
89
+ > retires a package-proven surface for lack of adoption, confirm the surface was
90
+ > *reachable*: named in the consumer's imports, or at least in a leaf it
91
+ > imports. Non-adoption of an unimported leaf is not evidence.
92
+ >
93
+ > The same audit produced this release's four consumer-driven changes (the tap
94
+ > target floor, the safe-area tokens, `ui-button__label`, and the dense tier)
95
+ > and the ADR-0001 canvas amendment. That is the intended loop: the consumer's
96
+ > local classes are the backlog, resolving either to a deletion there or an
97
+ > addition here.
98
+
71
99
  | Surface family | Current evidence | 1.0 disposition |
72
100
  | --- | --- | --- |
73
101
  | Core CSS, class recipes, vanilla behaviors, tokens, and Tailwind bridge | Downstream-proven across five inspected non-example app, site, and service consumers. | Stabilize names and behavior contracts. Use consumer upgrades as the release-candidate proof. |
74
102
  | Report, provenance, analytical CSS, annotations, glyphs, skins, workbench CSS, chart data, and Vega theme | Downstream-proven across four inspected report, site, dashboard, and tool consumers. | Keep opt-in. Stabilize the consumed paths; do not broaden the catalog during the freeze. |
75
103
  | Controlled non-`<dialog>` modal | Package-proven by stack, portal, late-node, focus, and cleanup regressions. None of ten inspected non-example consumers initializes `initModal`. | Deprecated in 0.7; remove no earlier than 0.8 unless a real consumer adopts it. Native `<dialog>` + `initDialog` is the stable path. |
76
104
  | React, Solid, Qwik, Svelte, and Vue lifecycle adapters | Package-proven by packed examples, types, and lifecycle tests. None of ten inspected non-example consumers imports an adapter entrypoint. | Deprecated in 0.7; remove no earlier than 0.8 unless a real consumer adopts one. Vanilla behaviors remain stable. |
77
- | Mermaid, D2, Shiki, Figma Variables, and the report-claims schema | Package-proven by generated-data, render, schema, and drift checks. No inspected non-example consumer currently supplies downstream proof for every path. | Keep compatible through 0.7.x, but decide each 1.0 contract from adoption evidence rather than generator coverage alone. |
105
+ | Mermaid, D2, Shiki, Figma Variables, and the report-claims schema | Package-proven by generated-data, render, schema, and drift checks. No inspected non-example consumer currently supplies downstream proof for every path. | Keep compatible through 0.8.x, but decide each 1.0 contract from adoption evidence rather than generator coverage alone. |
78
106
 
79
107
  After 1.0, breaking changes move to majors. Until then, the table below is the
80
108
  current public-surface matrix and the release policy above still applies.
@@ -85,6 +113,7 @@ current public-surface matrix and the release policy above still applies.
85
113
  | JS module format | Stable | JS subpaths are ESM-only. CommonJS consumers use dynamic `import()`. |
86
114
  | CSS class names (`.ui-*`) | Stable | Names and documented modifier semantics are public. Internal selector structure and leaf-file boundaries may change. |
87
115
  | Class recipes (`@ponchia/ui/classes`) | Stable | Exported `cls`, `ui`, `cx`, `attrs`, recipe names, ARIA attribute helper names, and option unions are public. |
116
+ | Severity ladder (`SEVERITY_LEVELS`, `severity()`, `.ui-severity*`) | Stable additive | The tier names and **their order** are public: `critical` › `error` › `warning` › `notice` › `ok`, with `unknown` deliberately outside the ordering as the fallback for "not measured". `severity()` returns `{ class, 'data-level' }`, and `data-level` is the public attribute contract — a host may carry it on its own element and read `var(--severity-tone)`. New tiers would be a breaking reorder, not an addition, so the ladder is closed; new *parts* (`chip`/`dot`/`row`) are additive. Ships in the opt-in `css/state.css`, not the default bundle. |
88
117
  | Class vocabulary as data (`@ponchia/ui/classes.json`, `@ponchia/ui/vscode.css-custom-data.json`) | Stable additive | The JSON shape (`groups`/`classes`/`states`/`customProperties`) and class/custom-property entries are public — for validating markup from a non-JS/non-TS host or editor integration. Generated from `cls` and CSS selectors; `classes.json`, `.d.ts`, reference docs, and VS Code custom data are drift-checked together. New classes/hooks are additive. |
89
118
  | Design tokens | Stable names/roles | Token names and documented roles are public. Exact values and generated colour math outputs may change for visual tuning before 1.0. |
90
119
  | `--accent-1..6` | Stable names/roles | A subtle-to-bold accent ramp derived from `--accent`. Exact resolved values are visual tuning; algorithm changes require release-note visibility and resolver/browser checks. |
package/docs/state.md CHANGED
@@ -124,14 +124,64 @@ For indeterminate jobs, omit the progress block or omit `aria-valuenow`, and
124
124
  make the written state clear ("Running", "Waiting for worker", "Retrying").
125
125
  Use `ui-job--compact` for dense queues.
126
126
 
127
+ ## Severity — `.ui-severity`
128
+
129
+ `.ui-state` answers *what is this thing doing*. Severity answers *how bad is
130
+ it*. Bronto already shipped the tones as per-component modifiers, but never the
131
+ **scale** — the tier names, their order, and the attribute carrying them. So
132
+ every consumer invents the ladder, and inside one app it drifts: one surface
133
+ saying `critical|error|warning|note`, the next `bad|warn`, a third
134
+ `critical|warning|info|ok`, under two different attribute names. Findings then
135
+ do not sort against alerts, and a filter written for one list misses the other.
136
+
137
+ The ladder, worst to best:
138
+
139
+ | Level | Means |
140
+ | --- | --- |
141
+ | `critical` | Broken now, and still losing something. |
142
+ | `error` | Something failed; it is not currently getting worse. |
143
+ | `warning` | A threshold was crossed; nothing has failed yet. |
144
+ | `notice` | Worth reading, no action implied. |
145
+ | `ok` | Checked and healthy — an **assertion**, not the absence of news. |
146
+ | `unknown` | Not measured, stale, or the check itself failed. |
147
+
148
+ `unknown` sits **outside** the ordering on purpose. It is not "slightly worse
149
+ than ok", it is "we do not know" — and collapsing it into `ok` is how a dead
150
+ collector reads as a healthy system. `SEVERITY_LEVELS` therefore excludes it.
151
+
152
+ The level travels on `data-level`, one attribute name, so the same selector
153
+ works on a chip, a row, a dot, or your own element via `var(--severity-tone)`:
154
+
155
+ ```html
156
+ <span class="ui-severity" data-level="critical">Critical</span>
157
+
158
+ <li class="ui-severity-row" data-level="warning">
159
+ <span class="ui-severity-row__title">Disk 84% on kpi-1</span>
160
+ <span class="ui-severity-row__meta">12m</span>
161
+ </li>
162
+ ```
163
+
164
+ Colour is never the only channel (WCAG 1.4.1): `.ui-severity` carries an
165
+ author-written label, and `.ui-severity-dot` is only for rows that **also** name
166
+ their level in text.
167
+
127
168
  ## Recipe
128
169
 
129
170
  ```js
130
- import { ui } from '@ponchia/ui/classes';
171
+ import { ui, severity, SEVERITY_LEVELS } from '@ponchia/ui/classes';
131
172
 
132
173
  ui.state({ state: 'saving', busy: true }); // "ui-state ui-state--saving ui-state--busy"
133
174
  ui.state({ state: 'conflict' }); // "ui-state ui-state--conflict"
134
175
  ui.job({ state: 'running' }); // "ui-job ui-job--running"
176
+
177
+ // Bundles the class WITH data-level, so the attribute that carries the meaning
178
+ // cannot be forgotten — the class alone paints the neutral tone and silently
179
+ // loses the level.
180
+ severity('critical'); // { class: 'ui-severity', 'data-level': 'critical' }
181
+ severity('warning', { part: 'row' }); // { class: 'ui-severity-row', … }
182
+ severity('nope'); // { class: 'ui-severity', 'data-level': 'unknown' }
183
+
184
+ SEVERITY_LEVELS; // ['critical','error','warning','notice','ok'] — sort/filter from this
135
185
  ```
136
186
 
137
187
  ## Scope
package/docs/theming.md CHANGED
@@ -140,6 +140,24 @@ you change CSS `--accent` later.
140
140
  [ADR-0003](adr/0003-theme-model.md) for the theme-model rationale.
141
141
  - **Radius** — `--radius-sm … --radius-xl`, `--radius-pill`. The Nothing
142
142
  default is near-sharp; raise these for a softer brand.
143
+ - **Tap targets** — `--tap-target` (44px, WCAG 2.5.5 / iOS HIG / Material) is
144
+ the floor every control floats to inside `@media (pointer: coarse)`;
145
+ `--tap-target-min` (24px) is the WCAG 2.5.8 AA minimum used by controls that
146
+ only have to clear the smaller bar. Both are authored as `max(px, rem)` on
147
+ purpose: they scale up with a larger root font but **cannot shrink below the
148
+ standard** if you re-point `html { font-size }`. If you override them, keep
149
+ the clamp — a bare rem is how a 44px floor quietly becomes 43.5px. Raise them
150
+ for a glove-friendly or kiosk build; do not lower them.
151
+ - **Safe areas** — `--safe-area-top / -right / -bottom / -left` default to
152
+ `env(safe-area-inset-*, 0px)`, so they are 0 everywhere except a device with
153
+ a display cutout or a gesture bar. Every viewport-anchored surface Bronto
154
+ ships reads them: the app rail and topbar, a sticky site header, the skip
155
+ link, both toast stacks, the drawer modal and the lightbox. Override them
156
+ when your host supplies its own insets — an embedded webview, a kiosk frame,
157
+ or a test runner that cannot emulate `env()`, which is the reason the values
158
+ are indirected through custom properties rather than called at the point of
159
+ use. Consumers positioning their own floating chrome should follow the same
160
+ convention: `inset-block-end: max(<your offset>, var(--safe-area-bottom))`.
143
161
  - **Type** — `--display` (dot-matrix face), `--mono`, `--sans`. Override
144
162
  to drop Doto or swap the body face; the token layer keeps working even
145
163
  if you self-host fonts (see the `fonts.css` note in the README).