mirror of
https://github.com/block/buzz.git
synced 2026-10-29 05:21:31 +00:00
fix(desktop-next): make coloured text legible on tint hovers, and widen the audit
A second pass over the colour system, checking the surfaces the first audit did not cover. The gap was tint hovers. DESIGN.md says a tint carries meaning and takes coloured text, but the audit only measured text against the four neutral surfaces — so `--bg-*-tint-hover`, the hardest surface in each coloured family, was never checked. Four pairings were below target: light danger (Lc 56.6) and warning (59.2), and dark accent and info (58.4 each). All four passed WCAG AA, which is why nothing else caught them. Size step 11 against the tint hover rather than the neutral panel, the same rule already applied to the neutral ramp: light danger-11 #b91c1c -> #a80008, light warning-11 #a16207 -> #9c5e00, dark accent-11 and info-11 #8fbaff -> #95c0ff. Minimum moves in OKLCH holding hue and chroma, so the hues do not shift. The audit now covers every tint and tint hover in all five families. Verified by falsification: reverting any one of the four fails the check. Two findings that are deliberately not changes: Hairline dividers stay at 1.2-1.8:1. WCAG's 3:1 non-text rule covers boundaries needed to identify a control or its state, not grouping lines; Radix ships 1.29 and Apple 1.26 for the same job. Raising them would draw the box the fill already implies. Recorded in DESIGN.md, along with the note that a control's own outline is a different question when the input layer lands. `--ring-focus` is an accent against accent: legible on every neutral surface at 3.0-3.5:1, but 1.1:1 on `bg-accent`, where it vanishes. Not yet reachable — the token is unregistered and unused — so the constraint is documented at the token instead of being solved speculatively: the component layer that introduces focus owns a surface-coloured offset, and there is no second ring token. Glass surfaces were checked by compositing each translucent value over the app gradient's own stops; all clear the target in both modes, which the relit neutral ramp is what earns. Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com>
This commit is contained in:
@@ -123,6 +123,15 @@ it is the rule a generated theme is measured against.
|
||||
every surface it can sit on, in both modes, parsed from `tokens.css` so the
|
||||
check cannot drift from the tokens. Exceptions live in that script with a
|
||||
stated reason, which keeps the list short and arguable.
|
||||
- **A tint's hover is the hardest surface in its family**, so a `text-*` step is
|
||||
sized against that rather than against the neutral panel. Every failure the
|
||||
audit found in the coloured families was on a tint-hover, never at rest.
|
||||
- **Hairline dividers are not held to a contrast target.** WCAG's 3:1 non-text
|
||||
rule covers boundaries needed to identify a *control* or its state, not
|
||||
grouping lines. Buzz's borders measure 1.2–1.8:1, which is where Radix and
|
||||
Apple ship theirs; raising them would draw the box the fill already implies.
|
||||
When the input layer lands, a control's own outline is a different question and
|
||||
does need the 3:1 treatment.
|
||||
|
||||
## Writing
|
||||
|
||||
|
||||
@@ -71,6 +71,13 @@ const PAIRS = [
|
||||
["--text-on-info", "--bg-info"],
|
||||
];
|
||||
|
||||
/**
|
||||
* The families with a tint surface. DESIGN.md: "tint carries a meaning and takes
|
||||
* coloured text", so each family's `text-*` must be readable on both its tint
|
||||
* and that tint's hover — the hover state is the harder one and was the gap.
|
||||
*/
|
||||
const TINT_FAMILIES = ["accent", "danger", "success", "warning", "info"];
|
||||
|
||||
const css = readFileSync(TOKENS, "utf8");
|
||||
|
||||
/**
|
||||
@@ -142,6 +149,10 @@ for (const [mode, map] of Object.entries(modes)) {
|
||||
for (const surface of SURFACES) check(role, surface);
|
||||
}
|
||||
for (const [role, fill] of PAIRS) check(role, fill);
|
||||
for (const family of TINT_FAMILIES) {
|
||||
check(`--text-${family}`, `--bg-${family}-tint`);
|
||||
check(`--text-${family}`, `--bg-${family}-tint-hover`);
|
||||
}
|
||||
}
|
||||
|
||||
// Guard the maths itself: if these drift, every verdict above is wrong.
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
--danger-3: #fecaca;
|
||||
--danger-8: #f5b5b5;
|
||||
--danger-9: #dc2626;
|
||||
--danger-11: #b91c1c;
|
||||
--danger-11: #a80008;
|
||||
|
||||
--success-2: #e8f7ee;
|
||||
--success-3: #d1f0dd;
|
||||
@@ -63,7 +63,7 @@
|
||||
--warning-3: #fde68a;
|
||||
--warning-8: #f2d98a;
|
||||
--warning-9: #eab308;
|
||||
--warning-11: #a16207;
|
||||
--warning-11: #9c5e00;
|
||||
|
||||
--info-2: #e7f1ff;
|
||||
--info-3: #d0e3ff;
|
||||
@@ -133,7 +133,7 @@
|
||||
--accent-3: #1a3358;
|
||||
--accent-8: #356dbd;
|
||||
--accent-9: #237eff;
|
||||
--accent-11: #8fbaff;
|
||||
--accent-11: #95c0ff;
|
||||
|
||||
--danger-2: #3a1515;
|
||||
--danger-3: #4d1c1c;
|
||||
@@ -157,7 +157,7 @@
|
||||
--info-3: #1a3358;
|
||||
--info-8: #356dbd;
|
||||
--info-9: #3d87ff;
|
||||
--info-11: #8fbaff;
|
||||
--info-11: #95c0ff;
|
||||
|
||||
--glass-1: #1c1c1c5c;
|
||||
--glass-2: #1c1c1c85;
|
||||
@@ -252,7 +252,13 @@
|
||||
--bg-panel-glass: var(--glass-2);
|
||||
--bg-float-glass: var(--glass-3);
|
||||
|
||||
/* --- Focus. ---------------------------------------------- */
|
||||
/* --- Focus. ----------------------------------------------
|
||||
Legible on every neutral surface (WCAG 3.0–3.5:1), but it is an accent
|
||||
against accent: on `bg-accent` it measures 1.1:1 and disappears. A focused
|
||||
primary button therefore needs the ring separated from the fill — an offset
|
||||
in the surface colour, not a different ring hue, so one focus treatment
|
||||
works everywhere. Whatever component layer introduces focus owns that
|
||||
offset; there is no second ring token. */
|
||||
--ring-focus: var(--accent-8);
|
||||
|
||||
/* ==========================================================
|
||||
|
||||
Reference in New Issue
Block a user