Skip to main content
Design

Color palettes fail in dark mode for a boring reason

A team ships a light theme, it looks good, then someone adds dark: classes, swaps backgrounds to slate-900, and the interface turns into glowing text on a void. Nothing is technically broken. Every contrast ratio was fine in light mode. The palette just was never measured against the dark background it ended up on.

The math does not care about taste

Contrast between text and background is a single number, the ratio between their relative luminance. WCAG wants 4.5:1 for body text and 3:1 for large text. Those numbers are checked per pair, not per theme. A gray that sits comfortably at 7:1 on white lands near 2.5:1 on a dark slate, and no amount of aesthetic judgment changes the arithmetic.

This is why "it looks fine to me" arguments about dark mode go nowhere. Two people, two monitors, two brightness settings. The ratio is the only shared fact in the room, and it is the thing screen readers, low-vision users, and anyone on a cheap phone screen in sunlight actually experience.

Where dark palettes go wrong in practice

  • Reusing the same gray for borders on both backgrounds. On white, a border needs to be light to be subtle. On near-black, that same value disappears or screams, depending on which direction the error goes.
  • Tinting dark surfaces with the brand hue. A navy-tinted background can look intentional in the mockup and muddy every accent color placed on it, because the accents were tuned against pure dark gray.
  • Trusting opacity stacking. A white overlay at 10% over an unknown surface color produces a different result every time the surface changes.

The practical check is unglamorous: list every foreground/background pair that actually occurs — text, muted text, borders, accents on each surface — and measure them with a contrast checker. Most teams find two or three pairs failing, usually muted text and placeholders, precisely the elements nobody puts in the mockup.

A shortcut that holds up

If you define your dark palette as ramps instead of single colors — one gray ramp for surfaces, one for text — most pairs stay valid by construction. Pick the text color two steps above the surface in the ramp, and the ratio lands in the safe zone without per-pair tuning. Verify with a color picker that what shipped in CSS is what the designer specified, because hex values get "adjusted by eye" in code review more often than anyone admits.

Dark mode is not a second brand. It is the same palette re-measured against different backgrounds, with the receipts kept as ratios rather than opinions.