“It’s not what it looks like!”

Reverse-engineering color perception

CSS Color Theory
By Ferdinand Vogler 19.03.2019 4 min read

If you have a dark blue button, you’d want the text on it to be white. Leaving those choices up the inexperienced user could create inaccessible color combinations by accident. At ahead we don’t want users thinking about contrast, so we let the machine do this.

Contrast is difficult

Humans perceive brightness differently depending on the color at use. A sharp yellow appears brighter then a mellow blue.

At first we converted the colors to the HSL format (hue, saturation, lightness). We then extracted the lightness value, this ranges from 0–100%, 100% being white. If it was >50%, our color function would return black as the contrasting color. If it was <=50%, it would return white.

Meet relative luminance

Most eyes perceive green shades more nuanced. Blue tones not so much. This has to be put into relation when calculating contrast. This is where relative luminance comes into play.

Y = 0.2126 * Red + 0.7152 * Green + 0.0722 * Blue
The formula reflects the luminosity function: green light contributes the most to the intensity perceived by humans, and blue light the least.Wikipedia

Sounds more complicated than it actually is. This means we have to convert colors to RGB and put red, green and blue into relation according to the equation quoted above.

// Calculate the perceived brightness
@function luma($color) {
  // Convert to RGB
  $R: red(rgba($color, 1));
  $G: green(rgba($color, 1));
  $B: blue(rgba($color, 1));
  // (De-)emphasize red, green and blue
  @return (0.2 * $R + 0.7 * $G + 0.07 * $B) / 255;

// Find out if the contrasting color is white or black
@function lumaContrast($colorToContrastAgainst) {
  $BACKGROUND: luma($colorToContrastAgainst);
  $WHITE: luma(#fff);
  $BLACK: luma(#000);
    @return #fff;
  } @else {
    @return #000;

// Usage
.myClass {
  background: #ffcc00;
  color: lumaContrast(#ffcc00);

Try it out yourself

See the pen by @fvo

Comment this article