Key insights
- Contrast is a ratio, not a color: the exact same off-white (#F0F0F0) reads crisp on a dark panel and disappears on a light one — the background decides legibility.
- Know the WCAG thresholds: aim for 4.5:1 on body text and 3:1 on large text. Below 3:1 the text degrades from 'large-only' to flat-out invisible.
- Most failures hide in 'decorative' muted grays — nav links, card labels, and secondary headings routinely sit at 1.5–2:1 and quietly fall below the line.
- Never encode meaning with color alone: for the ~8% of users with color vision deficiency, a red error and a green success collapse into the same muddy tone.
- Add a second signal alongside every color cue — an icon on error text, trend arrows on stats, or textures/patterns in charts — so the message survives when the color doesn't.
- A full pass is cheap: darken or lighten muted text to clear the ratio, then bolt an icon onto each state. Same layout, dramatically more readable.
Do / Don't
- Do: Check every text-on-background pair against WCAG — 4.5:1 for body copy, 3:1 for large text
- Do: Pair color with an icon, label, or pattern so state survives color blindness
- Do: Lighten or darken 'muted' secondary text until it clears the contrast threshold
- Don't: Rely on red-vs-green alone to separate errors from success
- Don't: Ship low-contrast grays for nav links and card labels just because they look sleek