Skip to content

Fix accordion/card-body contrast and outline-secondary legibility (Tokyo-11 sweep) - #456

Merged
WilfordGrimley merged 3 commits into
masterfrom
contrast-audit-tokyo11-2026-07-25
Jul 25, 2026
Merged

Fix accordion/card-body contrast and outline-secondary legibility (Tokyo-11 sweep)#456
WilfordGrimley merged 3 commits into
masterfrom
contrast-audit-tokyo11-2026-07-25

Conversation

@WilfordGrimley

Copy link
Copy Markdown

Description

Owner found real contrast/residual-grey defects on live proxyprints.ca after the Tokyo-11 re-theme (#438) — this PR is the mechanical site-wide audit the owner asked for, plus the token-layer fix.

Root causes (all in frontend/src/styles/styles.scss, wired before the Bootstrap/Superhero imports, same !default-preemption mechanism the rest of the theming layer uses, unless noted):

  1. $card-bg — Superhero's own _variables.scss sets $card-bg: $gray-600 !default;, a literal never actually derived from $secondary the way docs/features/theming.md assumed. Under the old Sitewide retheme: adopt the /display reference-mockup aesthetic (palette, shapes) across the app #302 palette the two values coincided so the bug was invisible; Tokyo-11 changed $theme-panel-bg without changing $card-bg, and every unstyled Card.Body/Accordion.Body (incl. the /contributions accordion body) rendered Superhero's raw grey. Fixed: $card-bg: $theme-panel-bg;. Also fixes $accordion-bg (Superhero: $accordion-bg: $card-bg !default;).
  2. $accordion-button-active-bg/$accordion-button-active-color — Superhero sets these to $primary/$body-color !default. Tokyo-11's light orange + light text = 1.26:1. Owner's ruling on the defect: the orange fill is correct, only the ink is wrong — same fix pattern as the existing button-ink flip. Fixed: both (+ the chevron icon colour) routed to $theme-btn-ink.
  3. .btn-outline-secondary — Bootstrap's outline-button generator necessarily reuses the same $theme-colors map entry the solid fill uses; Tokyo-11 intentionally repoints $secondary to a near-background dark tone (correct for fills, wrong as a foreground/border colour), so all ~38 .btn-outline-secondary mounts sitewide (the editor's "Showing: Fronts"/"Cardback", the restore-banner's "Dismiss") were ~1.2–1.4:1. Fixed by re-invoking Bootstrap's own button-outline-variant() mixin with $theme-light — one global override, not a per-component patch.
  4. $code-color — Bootstrap core's unrouted $pink default; every <code> tag sitewide (contributions guidelines, Syntax Guide) measured 3.19:1. Fixed: $code-color: $theme-info.
  5. $body-secondary-color/$body-secondary-bg.text-muted's unrouted translucent default. Fixed: routed to $theme-muted/$theme-panel-bg.
  6. Footer.tsx's ColumnHeading — a hand-written rgba(255,255,255,.4), not a Bootstrap default and not a token, measured 3.61:1. Routed to var(--theme-text).

Full writeup (with every measured ratio) in docs/features/theming.md's new "2026-07-25 contrast/residual-grey audit" section, which also corrects a wrong pre-existing claim about $card-bg and the $link-color "matches the pre-existing convention" claim (never actually measured until now).

Before/after failure table

Measured via the new frontend/tests/tooling/contrastAudit.ts (computed-style extraction, WCAG contrast against the owner's binding AAA bar — 7:1 normal, 4.5:1 large/bold), run across 10 states: /contributions (collapsed+expanded), the restore-draft banner, the Syntax Guide accordion (collapsed+expanded), the editor's mobile Print & Settings sheet, and a static-route sweep (/, /about, /explore, /myDecks, /whatsthat, /new) — all at 390px.

State Before (raw contrast failures) After
/contributions accordion header ("Contribution Guidelines") 1.26:1 (orange bg / pale-lavender text) 8.40:1 (dark ink)
/contributions accordion body Superhero default grey #4e5d6c $theme-panel-bg #2f3549
Restore-banner "Dismiss" button ~1.2:1 (outline-secondary) clears AAA
Print & Settings "Showing: Fronts"/"Cardback" ~1.2:1 (outline-secondary) clears AAA
<code> snippets (contributions, Syntax Guide) 3.19:1 7.09–8.49:1
Footer "Legal"/"Project"/"ProxyPrints" eyebrow labels 3.61:1 9.02:1
Total raw contrast failures across all 10 states 44 0 new (16 pre-existing/known, see below)

All 4 owner-reported defects: zero contrast failures after the fix, verified via a hard (non-allowlisted) assertion on the accordion header text specifically, plus screenshots (see Verification).

Open items (owner decision needed — not fixed by this PR)

Found by the sweep, deliberately left alone (either an already-ratified token's own known shortfall, a deliberate pre-existing convention needing an explicit replacement call, or a third-party library with no token seam):

  1. $theme-muted itself falls short of strict-AAA (7:1) on raised/panel backgrounds — 6.39/6.68/5.34:1 measured. This is PR Fix reduced-motion gap in AutofillCollapse chevron #432's own already-ratified compromise; this pass just routes more surfaces (.text-muted, Footer's eyebrow labels) onto the same token, so the same known number now shows up in more places, not a new regression.
  2. $link-color (defaults to $primary) measures only 5.98:1 on panel — WORSE than the accent purple docs/features/theming.md's own "Accent scope boundary" section rejected for prose (6.74:1) on the same kind of surface, and that claim was never actually measured until this audit. Needs an owner call on the replacement token — not fixed here since it'd change every plain <a> tag's colour sitewide.
  3. Two third-party libraries render unthemed defaults: react-select's placeholder (/myDecks, literal white #ffffff dropdown on this dark theme, 3.95:1) and react-dropdown-tree-select's tag pills (/explore, #dddddd/#000000). No Bootstrap/token seam reaches either — needs its own scoped override stylesheet, a distinct follow-up.
  4. $theme-danger as plain text measures 6.46:1 — the SAME already-documented AAA-large-only exception for danger-as-button-ink, just recurring as paragraph text (/whatsthat's error message). Not new.

These are captured in tests/tooling/contrastAudit.ts's isKnownOpenItem() allowlist (matched structurally, not by page) so the regression gate stays green without silently hiding them — they're logged via console.log on every run.

Checklist

  • I have installed pre-commit and installed the hooks with pre-commit install before creating any commits.
  • I have updated any related tests for code I modified or added new tests where appropriate. (frontend/tests/ContrastAudit.spec.ts + frontend/tests/tooling/contrastAudit.ts, a reusable audit tool + regression gate)
  • I have manually tested my changes as follows:
    • Ran frontend/tests/ContrastAudit.spec.ts before the fix (10/10 tests failing, 44 raw contrast failures) and after (10/10 passing, all remaining failures are the 4 documented open items above).
    • Ran a broader regression sanity sweep (DisplayLeftRailFidelity.spec.ts 17 tests, CardbackPdfWaitFidelity.spec.ts, GeneralUIAccessibility.spec.ts, GridSelectorModal.spec.ts, SelectVersionSection.spec.ts, ImportText.spec.ts, DisplayFinishFooter.spec.ts, Navbar.spec.ts — 111 tests total) — all green, confirming the $card-bg/accordion/outline-secondary/$code-color changes don't regress anything spec-locked (including the untouched outline-danger pill).
    • npx tsc --noEmit clean, npx prettier@2.7.1 --check clean on every changed file, next build compiles.
    • Screenshots at 390px (attached below): /contributions collapsed + expanded, the restore-draft banner, the editor's mobile Print & Settings sheet (scrolled to show Cardback).
  • I have updated any relevant documentation or created new documentation where appropriate. (docs/features/theming.md edited in place — new "2026-07-25 contrast/residual-grey audit" section, two CORRECTION notes on stale prior claims, a new "The audit tool" section, a new Verification entry)

Merge-time checklist

  • wiki: no user-facing page needs updating for this PR — it's a pure contrast/legibility fix to existing UI, not a new feature or workflow change.

…contrast audit tool

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@WilfordGrimley

Copy link
Copy Markdown
Author

Follow-up commit: link colour (owner-approved open item 2)

Owner asked to switch $link-color off $primary to $theme-accent, reasoning that accent's already-documented 6.74:1 (D14 band) beats $primary's 5.98:1 (panel) — measured on the SAME surface for every surface a link can land on, that comparison doesn't hold: $theme-accent is worse than $primary on all five surfaces, not better, and fails strict-AAA-normal (7:1) on four of them:

Surface $primary (old) $theme-accent (tried, rejected) $theme-info (shipped, resting) $theme-text (shipped, hover)
body-bg 8.40 7.39 9.96 10.59
raised-bg 7.16 6.30 ❌ 8.49 9.02
panel-bg 5.98 ❌ 5.26 ❌ 7.09 7.54
card-header-bg 5.99 ❌ 5.27 ❌ 7.10 7.55
band-bg 7.67 6.74 ❌ 9.09 9.66

Per this task's own "if any surface fails, do not force it, propose the minimal adjusted shade instead" instruction, $theme-accent was not used. Shipped $link-color: $theme-info instead — the codebase's existing "link-styled text" role token (already used for a handful of small cyan links, per docs/features/theming.md's "Accent scope boundary" section), no new palette entry, clears strict-AAA-normal on every surface with real margin. $link-hover-color explicitly set to $theme-text (Bootstrap's default 20%-darken auto-shift fails AAA once applied to a light colour on a dark theme — measured 4.56–6.40:1). :visited/:active need no separate rule (verified via Bootstrap core's _reboot.scss source — neither pseudo-class has its own rule, both inherit the resting $link-color).

Real production link verified (/contributions's ISO-639-1 Wikipedia reference, inside the accordion body): 7.09:1 resting / 7.54:1 hover.

New regression coverage: tests/ContrastAudit.spec.ts's "Link colour audit" describe block (2 tests — synthetic probes on all 5 surfaces + the real production link), both passing. Full ContrastAudit.spec.ts suite: 12/12 passing. docs/features/theming.md updated in place with the full writeup, including a note on the third "unmeasured claim" pattern this audit has now caught in this doc.

Not in this PR (owner-approved as a separate follow-up after this merges, to avoid two frontend branches colliding in styles.scss): the two third-party library findings (react-select, react-dropdown-tree-select).

@WilfordGrimley
WilfordGrimley merged commit 4c522e9 into master Jul 25, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant