Paint can being poured out

Web Colour Deep Dive

06 September 2020

I've been trying to work on my design skills lately. As much as some might be able to respect good code behind the scenes, everyone can tell when a website or application looks good.

There are so many different aspects to good design: layout, typography, alignment, usability and responsiveness, but lately I've been taking a look into colour (yes, this is how we spell it in New Zealand).

Having a look at Adam Wathan and Steve Schroger's advice on the subject, we can see we will need more than just five nice looking hex codes from a colour palette generator to get the right colours for an application.

They advise having a whole lot of shades of grey to choose from, one primary hue, and a few sparsely used accent hues. And having multiple levels of saturation and lightness from these for different levels of emphasis.

When I'm picking hex codes or RGB colours developing an app I get slowed down by trying to work out different levels of lightness and saturation from a single colour. I haven't had a good workflow. So to get these different levels from a hex code or RGB, it's been between using tools like hexcolortool or trying to carefully move the right ranges in VScode's visual editor.

HSL Values #

One way to get out of this situation is to use HSL values. HSL stands for hue, saturation, lightness. Using these you can declare your hue as a number from 0 to 359, then note down a percentage for each saturation and lightness. For instance:


div{
   background-color: hsl(155, 60%, 60%);
}

Gives you a muted mint green colour. HSL values are supported in all major browsers and a superior way of defining colours compared to RGB.

You could use these values to set your primary and accent colour hues, then you can iterate on the levels of saturation and lightness throughout your app. You know that as long as the hues you have chosen work together, your application should look consistent in its colouring.

This is because the hues are so central in making colour consistent. It's also why RGB and hex codes aren't as good a format - because they conceal the hue a colour is based on.

Another solution:

Colour Grid App #

To quote Refactoring UI again "As tempting as it is, you can't rely purely on math to craft the perfect color palette".

Naturally, after reading this, I built a react app to mathematically craft a colour palette. Okay, it won't solve all your problems, and it won't give you a full colour palette, but it will start you off with some options.

The app creates 100 different levels of saturation and lightness based off one hue.

When I looked, I couldn't really find an online tool which worked like this. There are 16,581,375 colours to choose from on the web and I'd prefer to have some kind of system to limit these down but still enough options to flesh out a UI.

Using the colour grid, I have a defined set of options which I can use to emphasise or deemphasise different parts of an application or website.

Also, here are some things I learnt along the way:

How to find the Lightness of an RGB Colour #

The level of lightness of an RGB colour can be worked out by finding the average of the highest and lowest of the RGB values divided by 255 (the middle colour does not affect the lightness).

This will give you a decimal between zero and one which is the degree of lightness the RGB colour is.

Disclaimer: This technique does not account for luminance. Luminance is the inherent brightness of a hue. It's illustrated by the fact pure yellow looks a lot brighter to us than a pure purple.

My technique here will get you the level of lightness based on a programmatic measure of how close the RGB values get the colours to white or black, but perceived brightness is affected by more than just this.

Nonetheless, if you want to get an estimate of the lightness of an RGB colour, here is a javascript function you can use:


function getLightnessOfRGB(rgbString){
    //First convert to an array of integers by removing the whitespace, taking the 3rd char to the 2nd last then splitting by ','
   const rgbIntArray(rgbString.replace(/ /g, '').slice(4, -1).split(',').map(e => parseInt(e)));

    //get the highest and lowest out of red green and blue
   const highest = rgbIntArray.indexOf(Math.max(...rgbIntArray));
    const lowest = rgbIntArray.indexOf(Math.min(...rgbIntArray));

    //return the average divided by 255
    return (rgbIntArray[highest] + rgbIntArray[lowest]) / 2 / 255;
}

How to lighten an RGB colour keeping the hue the same #

To lighten an RGB value and keep the hue the same, you need to increase each RGB value by the same proportion of difference between the value and 255.

What does this gibberish mean?!

I think it's easier to explain with an example. Lets say we have this colour: rgb(0, 153, 255). That's a fully saturated cyan-y blue. Lets look at the difference between each RGB value and 255: red is zero so the difference is 255. Green is 153 so the difference is 102. Blue is 255, so the difference is zero.

Now when we lighten the colour, we need to increase each RGB value by the same fraction of our differences. Let's increase lightness by a tenth.

This means we need to divide each difference between the RGB value and 255 by 10, then add this amount to the RGB value. So to red we'll add 255 / 10 (25.5), to green we'll add 102 / 10 (10.2), and to blue we'll add 0 / 10 (zero).

This means the colour we end up with is: rgb(26, 163, 255). That's still the same hue, but a touch lighter.

Here's a javascript function which will do this for you:


function lightenRgb(rgb) {
   //Our rgb to int array function again
   const rgbIntArray = rgbString.replace(/ /g, '').slice(4, -1).split(',').map(e => parseInt(e));

   // Map over each array item and return the value, plus the difference between the value and 255 divided by 10
   const returnArray = rgbIntArray.map(rgbIntArray,value=>{return (value + ((255 - value) / 10))});

   //convert the array back into an rgb string
   return (`rgb(${returnArray.join()})`);
 }

How to darken an RGB colour keeping the hue the same #

Conversely, darkening is pretty similar. Just replace adding to the values to make 255, with subtracting to make zero:


function darkenRgb(rgb) {
   //Our rgb to int array function again
   const rgbIntArray = rgbString.replace(/ /g, '').slice(4, -1).split(',').map(e => parseInt(e));

   // Map over each array item and return the value, plus the difference between the value and 255 divided by 10
   const returnArray = rgbIntArray.map(rgbIntArray,value=>{return (value + ((0 - value) / 10))});

   //convert the array back into an rgb string
   return (`rgb(${returnArray.join()})`);
 }

So if you ever do need to lighten or darken an RGB colour keping the hue the same, those are some useful functions. Also give Colour grid or HSL values a try.

Thanks for reading.

Laters,
-Hugh

Back to blog