Contrast belongs to the pair, not the color

Lesson 2 · utune-ai.vercel.app · Measure every text/background pair, then fix only the failing pairs.

The real reason uTune AI is hard to read — in low light or any light — is that its most-used body-text gray, #9aa0aa (97 occurrences), sits on white and cream at roughly 2.5:1 contrast, when readable body text needs 4.5:1. This lesson's win: you can measure any pair in Chrome DevTools, quote the thresholds from memory, and name the two fixes this site needs.

The rule: WCAG AA thresholds

WCAG 2.2 success criterion 1.4.3 (Contrast, Minimum) sets two numbers:

Text kindMinimum ratioOn this site
Body text (under ~24px regular / ~18.5px bold)4.5:1All the gray copy, nav links, card metadata
Large text (≥24px regular or ≥18.5px bold)3:1The 40–64px hero headline

Ratios run from 1:1 (identical colors) to 21:1 (black on white). The ratio is computed from relative luminance, so it needs both colors — which is why the same foreground can pass in one place and fail in another.

Measure it in Chrome DevTools

  1. Right-click the pale “Search › Enrich › Analyze › Close” kicker text → Inspect.
  2. In the Styles pane, find the color declaration and click its little color swatch.
  3. The color picker shows a Contrast ratio row. Expand it: you get the computed ratio against the rendered background, AA/AAA checkmarks, and two white curves on the color field — drag the color below the top line and it passes AA.

Two power-ups: the Lighthouse accessibility audit lists every failing pair on the page at once, and the Rendering tab → Emulate vision deficiencies → Blurred vision gives you a fast “low-light squint test.”

The measurements for this site

WCAG ratios for uTune AI's actual pairs (computed from the served HTML/CSS).

Listen first: four pairs fail. The muted gray 9aa0aa scores about two-point-six to one on white, two-point-five on cream, and two-point-three on light gray — all far below four-point-five. White hero text on the sky gradient scores between one-point-five and two to one. Meanwhile the same 9aa0aa gray scores seven-point-two to one on the dark section background, a comfortable pass — proof that the color isn't broken, the pairing is.

Read that table twice: #9aa0aa fails on cream and passes on the dark mockup cards. Contrast is a property of the pair. So the fix is not “delete this color” — it's “stop pairing it with light backgrounds.”

Fix 1 — darken the muted gray on light backgrounds

Listen first: replace the muted gray 9aa0aa with 646a78 wherever it sits on a light background. That value was chosen by testing it against all six light backgrounds the site uses; its worst score is four-point-five-nine to one, so every pairing passes AA. Occurrences inside the dark mockup cards keep the original gray.

The one-value fix, verified against every light background on the page.

If you'd rather consolidate than add a value: the site's existing #6b7180 passes on white and cream, but only reaches 4.29 on #f0f0f0 — so it can't be the single universal muted tone. #646a78 can, and it keeps the same cool-gray character. (This is also the moment to name each value's role — “muted-on-light” vs “muted-on-dark” — which is the token thinking from Lesson 1.)

Fix 2 — the hero headline over the sky

White at 40–64px is “large text,” so it only needs 3:1 — and it still fails (1.5–2.0:1). Three honest options, in order of least design disruption:

  1. Use ink instead of white: the site's own #0f1115 scores 9.5:1 on the sky top and 12.9:1 mid-gradient. One class change, guaranteed pass.
  2. Put a scrim behind the text: a soft dark overlay (e.g. rgba(15,17,21,0.55) panel or a bottom-up gradient) restores white text to a passing background. The existing drop-shadow(0 1px 2px rgba(0,0,0,0.15)) is far too weak to count.
  3. Darken the sky's top stop so white passes: even #2f7fc4 only reaches 4.24:1 (passes 3:1 large-text, fails for the 13.5px white nav links) — so if the nav stays white, it needs its own scrim or pill background regardless.

Note the trap in option 3: the headline and the 13.5px nav links share the sky but not the threshold — the nav needs 4.5:1. Check each pair at its own threshold.

“It looks fine on my screen.” Your calibrated, bright desktop monitor is the best case. Low-light phone use, night-shift mode, cheap panels, and older eyes all push effective contrast down — the 4.5:1 floor exists to keep text readable in those conditions, not the ideal ones.
Cold-recall defense (one breath):
Contrast is a pair property — #9aa0aa passes on the dark cards (7.2:1) and fails on cream (2.5:1) — so the fix is to darken it to #646a78 on light backgrounds (AA everywhere: 4.59–5.42) and give the hero headline ink text or a scrim, checking body text at 4.5:1 and large text at 3:1.

Read next

Primary source

W3C — Understanding SC 1.4.3: Contrast (Minimum) (~10-min read): the definitive statement of the 4.5:1/3:1 thresholds, what counts as large text, and the exceptions.

💬 I'm your teacher for this — ask me followups any time.

Lesson 2 · workspace utune-ai · mission: make utune-ai.vercel.app easy to read in low light