diff --git a/desktop-next/DESIGN.md b/desktop-next/DESIGN.md index 1a04005a72..abdbe2c3bf 100644 --- a/desktop-next/DESIGN.md +++ b/desktop-next/DESIGN.md @@ -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 diff --git a/desktop-next/scripts/check-contrast.mjs b/desktop-next/scripts/check-contrast.mjs index 9a75a75557..7cd81f171d 100644 --- a/desktop-next/scripts/check-contrast.mjs +++ b/desktop-next/scripts/check-contrast.mjs @@ -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. diff --git a/desktop-next/src/shared/styles/tokens.css b/desktop-next/src/shared/styles/tokens.css index 9d3605e4f5..50e4176969 100644 --- a/desktop-next/src/shared/styles/tokens.css +++ b/desktop-next/src/shared/styles/tokens.css @@ -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); /* ==========================================================