Contrast headroom of the candidate palette space #50
Notifications
Due Date
No due date set.
Blocks
#53 Palette candidates on a dense page
ShadowsOverWestgate/sow-nodebb-theme
Reference: ShadowsOverWestgate/sow-nodebb-theme#50
Reference in New Issue
Block a user
Part of #47
Question
Which candidate palette combinations can actually hit WCAG AA, and where is the headroom?
AA is non-negotiable on this map, and it is the constraint that kills palettes late if it is not checked early. Establish the numbers before any prototype is built.
Inputs — current tokens in
DESIGN.mdfrontmatter andscss/westgate/_tokens.scss, plus the collected references in../_media/theme-color-palettes/. Candidate space:#351E28(confirmed to supersede Bruised Plum), plus near-blacks#141214,#1B1211,#110E0D,#0F0D12(current),#09080B(current).#5B1E32,#3E1617,#340C0C,#22040D,#402533,#311B1D.#C2A35A, Old Gilt#A8893F, Gold Lit#E0C878, and the warmer Antique Gold#C28D39.#7F8995/#636D79/#9EA7B3, and warm iron/brass alternatives.#EFE5D2champagne,#B59785butcher paper.#E6E0D6,#B9B2A6,#9A9086,#D8C28A.Report, as a table of measured ratios:
Note where the current theme is already close to the floor — placeholders are documented as sitting at 74% alpha precisely because that is the 4.5:1 edge, which means there is no slack there.
Resolved
Full findings:
docs/research/palette-contrast-headroom.mdon branchresearch/palette-contrast-headroom(not pushed, not PR'd — commit91cfd1a).Every number below is computed WCAG 2.x relative luminance,
(L1+0.05)/(L2+0.05), with alpha colours flattened over their real backdrop first (source-over, sRGB). The script self-checked against WCAG's own published values (#000/#fff= 21.0,#767676on white = 4.54,#949494on white = 3.03,#959595< 3.0) before producing any palette number.Headline
#5B1E32), clear 4.5:1 with Bone, Ash and Ledger Ink. Dark surfaces are not the constraint.#351E28is safe and it is the ceiling. L=0.0184. The Dust-Text body-copy ceiling is L=0.0245. There is almost no room above Plum Noir.#B59785cannot coexist at all — all four golds sit at 1.08–1.22:1. Hardest kill in the space.1. Text ramp on the base darks — no failures
#351E28#2A1222#141214#1B1211#110E0D#0F0D12#09080B#18141D#110F15#201924#E6E0D6#B9B2A6#9A9086#D8C28A#A99D8FAll ≥4.5:1. Plum Noir's weakest pair (Dust, 4.90) has +0.40 of slack. Plum Noir superseding Bruised Plum costs 0.6–1.5 points across the ramp and breaks nothing.
2. On the oxblood/wine/plum mids — Dust breaks
#5B1E32#3E1617#340C0C#22040D#402533#311B1D#3A1830Rule that falls out: oxblood and plum-mid are accent surfaces, not body-copy surfaces. Put a timestamp on them and you are out of AA.
3. Gold as body/link text — the three-gold verdict
#C2A35A#A8893F#E0C878#C28D39#C2A35Aeverywhere; +0.74 slack on Plum Noir; fails on oxblood (4.27). Not a free swap for the warmth.4. Second metal, neutral pewter only (no lavender/purple-cast)
#9EA7B3#7F8995#636D79Catch: the pewters are luminance twins of the golds.
#9EA7B3L=0.3816 vs Tarnished Gold L=0.3840 — 1.006:1 between them. A two-metal hierarchy has to work by hue and placement; contrast will not help it.5. Focus ring, SC 1.4.11 (3:1)
Current ring is
outline: 2px solid #c2a35awithoutline-offset: 2px(_controls.scss:672). The offset means the ring's adjacent colour is the surface behind the control, not its fill.#EFE5D2#B59785#C2A35A#E0C878#C28D39#A8893F#9EA7B3#636D79#09080BComposited surfaces change nothing: ring vs velvet-panel
rgba(24,20,29,.97)/bg = 7.50, vs ledger-panel = 7.70, vs input wellrgba(18,15,22,.96)/panel = 7.85.Three findings: (a) gold has real headroom on the darks, 1.7× the requirement at worst — the ring does not need to change. (b)
outline-offset: 2pxis load-bearing — at offset 0 or inset, gold measures 3.57 on the primary plate#5A4A1Aand 2.90 on the hover plate#6A5720, a fail. It is an a11y mechanism, not spacing taste. (c) no single ring colour spans dark and light; a light surface forces a surface-aware ring token.6. If a light surface enters
The whole existing ramp is unusable: best on champagne is Dust at 2.51, best on butcher paper is Bone at 2.07. A new dark ink token is forced — structural, not tunable.
#EFE5D2#B59785#09080B#0F0D12#351E28#5B1E32#8E3438#A84A4ECheap answer: Plum Noir doubles as the light-surface ink. One new role (
ink-on-light: #351E28), no new hex — 12.29 on champagne, 5.65 on butcher paper.Two more light-surface facts: champagne vs butcher paper is 2.17:1, so they cannot sit adjacent without a border. And butcher paper is a plain-paper inset or nothing — no metal, no accent, no state colour clears anything on it.
7. The 74% placeholder claim — it does not hold
$input-placeholder-color: rgba($wg-text-soft, 0.74)(overrides.scss:75), composited over the real input well (rgba(18,15,22,.96)over#18141D, flattened#171319):#76716D#87817B#8E8881#B9B2A6Edge alpha on every other plausible backdrop: bg-deep 0.653, bg 0.656, input well 0.659, panel 0.668, panel-3 0.683. Worst case 0.683, so even α=0.70 passes everywhere.
Verdict: the claim is false and should be corrected in DESIGN.md:205. Leaving it is worse than a wrong colour — it makes the re-tune treat a comfortable value as untouchable and go hunting for slack somewhere that genuinely has none. (Same for DESIGN.md:222 — "Ash on Crypt Black is the floor" measures 9.19:1; it is the floor by intent, with 4.69 of slack.)
8. Where the theme really is at the floor
hljs-deletion#C55A5Fon code well, light end_posts.scss:215), so a re-tune will not carry it#8E3438fill vs surface_controls.scss:620) — its text is fine at 5.99, the badge shape is not#A84A4Eornament on ledger-panel#C55A5Fas text on bgEverything else has ≥1.3 of slack (badge text 5.99, primary-button text 7.00, ghost text 11.86,
::selection8.97, Ledger Ink 10.66, statuses 8.25–9.73).The alpha borders are all below 3:1 and that is fine —
--wg-border.14 → 1.26,--wg-border-soft.08 → 1.13, ledger/input border .18 → 1.34–1.35, code border .22 → 1.46, ruling .055 → 1.08,--wg-focusglow .28 → 1.67. SC 1.4.11 covers boundaries needed to identify a control; these are decorative and the control is identified by its fill, label and the solid ring. But it caps ambition: solved exactly, gold needs α ≈ 0.57 to reach 3:1 (0.529 input well, 0.533 panel, 0.541 panel-3, 0.565 Plum Noir). Anything in the 5.5–28% range is atmosphere only, permanently.9. Impossible at AA — do not prototype these
#B59785#EFE5D2#636D79on Plum Noir as ink/icon/border/ring#636D79on oxblood/plum-mid/plum-bloom/#3E1617#9A9086as body copy on oxblood or plum-mid#5A4A1A, 2.90 on#6A5720#C2A35AL=.3840 vs pewter#9EA7B3L=.3816)10. Candidate rules for #49 / #55
#351E28is safe and is also the ceiling — do not lighten any surface that carries small text.#9EA7B3.#7F8995conditional,#636D79out.ink-on-light: #351E28) plus a second focus-ring token. Budget both or drop it.hljs-deletionat 4.51:1, and the wrong 74% claim at DESIGN.md:205.outline-offseton the focus ring — it is an accessibility mechanism.Caveat worth carrying forward
WCAG 2.x relative luminance is a crude perceptual model, known to be harsh on light-on-dark pairs. These are the conformance numbers, which is what "AA is non-negotiable" means and what an audit will use — not a promise that a 4.6:1 pair looks comfortable. Anything measuring under ~5:1 in these tables should still be looked at in a browser before it ships. Gradients were measured at their end stops only; anti-aliased thin gilt rules render below their nominal alpha, so the §8 border numbers are optimistic if anything.