The audit says your brand blue fails on white. The brand team says the blue is non-negotiable. Both sides can win, because of one underappreciated fact: contrast comes almost entirely from lightness, not hue. You can keep the hue — the thing people recognize — and fix the ratio by adjusting only how light the color is.
Why lightness is the whole game
A contrast ratio compares luminance — perceived brightness. Two colors with the same luminance have a ratio near 1:1 no matter their hues: a pure red and a pure blue of equal brightness are unreadable on top of each other. So rotating hue to fix contrast is pushing on a rope. Lightness is the lever.
The OKLCH method
Modern CSS gives every color three perceptual dials: oklch(L C H) — lightness, chroma (saturation), and hue. The fix is mechanical:
- Convert your color to OKLCH.
- Hold H (hue) and C (chroma) fixed.
- Move L — darker for text on light backgrounds, lighter on dark — until the pair clears its threshold (4.5:1 for body text).
A worked example with a saturated azure, azure-family #1392e6 on white:
#1392E6text#FFFFFFbackground#0072BDtext#FFFFFFbackgroundSame hue, same chroma; lightness lowered from L≈0.62 to L≈0.53. Still unmistakably the same blue.
If darkening isn’t an option
Sometimes even a small lightness shift is off the table — say, the exact hex is in the logo guidelines. You still have moves:
- Flip which side changes. Keep the brand color as the background and adjust the text instead. Brand blue buttons with white text often pass while blue-on-white text fails.
- Reserve the pure brand color for large text and graphics. Large text only needs 3:1, and decorative elements need none — use the exact hex there, and the darkened variant for body-size text.
- Ship a two-token system.
--brand(the sacred hex) and--brand-text(the accessible variant). Most design systems land here; nobody outside the design team ever notices the two blues differ.
Two traps
- Fixing with opacity. Dropping white text to 80% opacity to “soften” it on your brand color changes the rendered color and usually drags a passing pair under the line.
- Yellow, cyan, and lime. Some hues are intrinsically light — a yellow dark enough to pass 4.5:1 on white stops looking yellow. For those, put the color in the background with dark text instead. (Black on yellow is 19.6:1.)
Try it in the tool
PaletteSense automates the whole method: the contrast checker shows the failure, and the optimize-contrast API endpoint nudges lightness in OKLCH — hue and chroma untouched — and returns the nearest passing color. The fix suggestions on pages like gray on white are computed exactly this way.