home about me

Finding optimal color contrast

When it comes to styling text content, readability trumps everything else. But not all colors are made equal.

No matter how fancy, the most impressive design is worth nothing if the text is hard to read. You don’t want to force your visitors into Reader Mode - or worse, have them look for information elsewhere.

There are a lot of things that go into making good-looking and easily-readable text content, but today I want to focus on color. If the contrast between text and background is too low, it will put strain onto the eyes and demand more concentration.

Color contrast is so important, the Web Content Accessibility Guidelines prescribe a minimum contrast of 4.5:1 for text content.
The Google Chrome DevTools will warn you if the contrast is too low, and the Lighthouse test will mark low contrast as an accessibility problem.

Humans perceive the three primary colors with different luminance. This is why there are different algorithms for converting full-color images to grayscale - if you’ve ever played around in Photoshop, you’re familiar with this. Put simply, this means „100% green“ and „100% red“ are perceived with a different brightness, even though they emit the same amount of light.

Take a look at these example paragraphs, which have - mathematically - roughly the same contrast ratio (4.5 vs 4.66, according to Chrome Devtools), but one is way harder to read than the other:

‘What’s happened to me,’ he thought. It was no dream. His room, a proper room for a human being, only somewhat too small, lay quietly between the four well-known walls. Above the table, on which an unpacked collection of sample cloth goods was spread out (Samsa was a traveling salesman) hung the picture which he had cut out of an illustrated magazine a little while ago and set in a pretty gilt frame.

Gregor’s glance then turned to the window. The dreary weather (the rain drops were falling audibly down on the metal window ledge) made him quite melancholy. ‘Why don’t I keep sleeping for a little while longer and forget all this foolishness,’ he thought. But this was entirely impractical, for he was used to sleeping on his right side, and in his present state he couldn’t get himself into this position.

The National Television System Committee suggests a conversion using weighted values:
Red gets a weight of 0.299, Green 0.587 and Blue 0.114.

Using these weights, it’s easy to come up with a SCSS mixin to set the text color based on the background color, thanks to SCSS’s native color handling capabilities:

@mixin readable-text-bg($bgcolor, $darkcolor: 'black', $lightcolor: 'white') {
  $contrast: (red($bgcolor) * 0.299 + green($bgcolor) * 0.587 + blue($bgcolor) * 0.114);
  @if $contrast >= 128 {
    color: $darkcolor;
  } @else {
    color: $lightcolor;
  }
	background-color: $bgcolor;
}

The same simple logic could of course also be used in Javascript or any other language you desire:

//takes an object with r, g and b properties that are from 0 to 255
function readable_text(bgcolor) {
  if ((bgcolor.r * 0.299 + bgcolor.g * 0.587 + bgcolor.b * 0.114) >= 128)
    return 'black'
  else
    return 'white'
}

If you want to see this code in action or play around with it, you can do so in this Codepen, courtesy of Franz Kafka.