A better way for dynamically choosing a readable text colour with Sass

Have you ever styled a button with Sass and made it change its text colour according to its background colour? Did you use the lightness() function? There’s a better approach; here’s a bit of “kitchen colorimetry” and a few pieces of reusable code for your projects.

I’ve often seen Sass tutorials introduce conditionals with a particular snippet of code similar to the one below.

The following piece of code checks if the lightness value of a background colour is greater than 50%. It then sets a readable text colour: black if the condition is true, white if it’s false.

There’s nothing wrong with teaching conditionals with that. It’s easy to read and understand, and like best examples, it’s applicable to real life.

It has a problem, though, and that problem is that people start using it in their projects. I did too, until I realised that setting $button-color to e.g. blue will produce black text that is then completely illegible!

Fiddling with the threshold won’t help much. There’s always some case that triggers the wrong text colour.

To see what I mean more clearly, take a look at the demo below.

See the demo on CodePen

In this eye-watering demo, readability varies wildly despite each of the backgrounds having the exact same lightness value. White is easy to read on blue, but very difficult to read on yellow.

Using Google-fu to find an explanation

What’s the catch then? After a bit of searching, I found a great Thoughtbot article called A Closer Look at Color Lightness by Reda Lemeden. I quoted a few of his handy definitions.

The article explains the situation better than I ever could, so go take a look at it.

Odd behaviour occurs, because lightness is a non-weighted measure. In the HSL colour model for example, changing the hue will change the perceived brightness, but the lightness value will stay the same. This makes comparing lightness an unreliable method for judging perceived brightness, and by extension for judging readability.

After running some tests that you can also try yourself, Lemeden recommends using a weighted measure named sRGB luma as a good approximation for most use cases. The article has a fantastic graph of the different methods tested.

The snippets he shared include a function for Sass, but that one only returns if a colour is light or dark. It’s useful, but I wanted a one-liner so I could avoid writing ifs all the time. I decided to do some modifications to it.

The solution

This first one, _luma.scss, just calculates the luma of a colour based on the equation from the article.

The second one is where the logic happens. It provides a function with the signature pick-visible-color($bg-color, $color-1, $color-2).

It takes a background colour and two potential (e.g. text) colours. It then calculates their luma values and returns the colour that has the greater luma difference compared to the background colour.

Copy and paste the previous gists or download the raw files into your project’s files. I recommend creating a separate directory for them and other reusable functions.

Import the function into your file. Now you can simply write color: pick-visible-color(...) and you’ll get a contrasting colour combination. Neat!

Below is a quick example if you need clarification.

Final shout-out

While researching colour perception for this article (and subsequently just for fun), I found this fascinating “Colors and Colorimetry” overview by David Madore. It explains light, colour, the concept of “white” and more. Go check it out if you’re interested.

Related posts

Leo Nikkilä
Hello, my name is Leo Nikkilä.

I’m a mobile and web developer freelancing worldwide from Helsinki, Finland.

Right now I’m working with Android and Kotlin, and exploring functional programming with Elixir and Elm.

Send me mail at [email protected] or a message on Twitter!

Interested in hiring me?

Do you need quality code for your next big thing? Some extra oomph to your team?

Currently I’m not immediately available for new work, but feel free to contact me anyway and I’ll see what I can do.