Skip to main content

HSL: a color format for humans

By Paul Hebert

Published on March 12th, 2021

Topics

Colors on the web are confusing — but they don’t have to be! The HSL format makes it easy for humans and computers to work with color.

When I was learning web development, color codes felt like magic spells: #000000 meant black, #ff0000 meant red, and #ffffff meant white. These are called hexadecimal colors and they’re super confusing.

When I learned about HSL colors it was a revelation: a color format I could understand and manipulate with code!

So what’s the difference between these color formats? And why do I find HSL so much easier to understand?

The first reason I find hexadecimal colors confusing is because they require me to think about mixing colors in a way I’m not use to.

A young blond-haired boy mixing purple paint with his hands at a messy table covered in arts and craft supplies.
Young me, blissfully unaware of how confusing color theory was going to get.

When I was in kindergarten I learned how to mix different colors with paint. By combining the three “primary colors” (red, yellow, and blue) I was able to make whatever colors I wanted! Red plus yellow made orange, yellow plus blue made green, and blue plus red made purple.

However, our computer screens don’t make colors by mixing paints. Instead of light reflecting off of physical paints, screens create colors by emitting red, green, and blue light. (These different color models are known as subtractive and additive colors.)

I’ve been working with colors on screens for years but this difference still confuses me. Instead of mixing red, yellow, and blue paint I need to mix red, green, and blue light. What combination of red, green, and blue makes yellow? (It turns out the answer is lots of red and green light. Who knew?)

Unfortunately this color model is central to how hexadecimal colors work.

To better understand how hexadecimal colors work let’s take a detour and explore their cousins, RGB colors.

We can use the RBG syntax to represent digital colors: rgb(15, 213, 133). The first number represents the amount of red light, the second is green light, and the third is blue light.

There’s also an RGBA syntax which adds an additional “alpha” channel to control opacity. (A low alpha value is very transparent and a high alpha value is very opaque.) Here’s what that looks like: rgba(15, 213, 133, 0.5).

You can play with this RGBA color picker to get a better sense of how these colors interact:

The hexadecimal format is another way to represent RGB colors that adds a confusing twist.

Each color is represented using a “hexadecimal” value. This means all of the numbers are represented using base 16 instead of base 10. For example 219 is DB and 112 is 70. rgb(255, 255, 255) would be #ffffff.

Decimal 1 2 9 10 11 12 13 14 15 16 17 255
Hexadecimal 1 2 9 a b c d e f 10 11 ff

Are you feeling more confused now? Exactly!

Like RGBA, hexadecimal colors have an optional alpha channel for opacity. Here’s what a hexadecimal color with an alpha channel looks like: #000000ff. (The alpha channel is not supported by Internet Explorer.)

Here’s a hexadecimal picker for you to play with:

These color codes are compact and work well for computers, but they’re confusing for humans. To understand the color #a63fd3 my brain needs to go through the following process:

  1. Split the color into red, green, and blue sections: (red: a6, green: 3f, blue: d3)
  2. Convert hexadecimal values to decimal values: (red: 166, green: 63, blue: 211)
  3. Try to figure out what color that combination of red, green, and blue light creates: (It’s kind of a lavender color?)

This is the most common color format on the web, but it’s super confusing. Luckily, there’s a better option out there!

The HSL color format is much closer to how I think about colors. It defines colors in terms of their hue, saturation, and lightness:

  • Hue refers to the overall color. For example, red, orange, yellow, green, blue, and purple. (The hue ranges from 0 to 360.)
  • Saturation describes how vivid or intense a color is. A low saturation color would appear gray or washed out, while a high saturation color would appear intense and colorful. (The saturation ranges from 0% to 100%.)
  • Lightness describes how light or dark a color is. Black has a very low lightness. White has a very high lightness. (The lightness ranges from 0% to 100%.)

Here’s what an HSL color looks like: hsl(180, 50%, 50%). Similar to RGBA, there’s also an HSLA format which adds an alpha channel to handle opacity: hsla(180, 50%, 50%, 50%).

You can play with this HSLA color picker to get a better sense of how these properties interact:

This matches how I think about color: “I want a dark grayish-blue” instead of “I want 180 parts blue, 20 parts red and 30 parts green.” If I want to make a color slightly darker or slightly more saturated, I change one property instead of mucking about with three different colors.

There are two color pickers below: an HSL color picker and a hexadecimal color picker. Try to make each one match the color up top. Try changing the color a few times.

Which one is easier for you? The HSL picker or the RGB picker?

This may not seem like a big deal. Hexadecimal colors are short, they work, and they’re used everywhere. Why change what’s working?

They lower the barrier to understanding color on the web. They make it easier for developers to understand color and designers to understand code. They make it easier to communicate and collaborate around color.

If we can break a color down into its hue, saturation, and lightness we can manipulate those properties with code.

By combining HSL with CSS custom properties we can dynamically theme components:

:root {
  /* A nice, blue hue */
  --hue: 200;
  /* A dangerous, red hue */
  --hue-danger: 0;
}

a {
  /* A saturated blue */
  color: hsl(var(--hue), 90%, 40%);
}

a.danger {
  /* By swapping our hue we can switch to a saturated red */
  --hue: var(--hue-danger);
}

a:hover {
  /* A more saturated, darker version of the current hue */
  color: hsl(var(--hue), 100%, 30%);
}
Code language: CSS (css)

We can use math to manipulate colors in CSS:

:root {
  --hue: 200;
  --base-saturation: 90%;
  --base-lightness: 40%;
}

a:hover {
  color: hsl(
    var(-hue),
    calc(var(--base-saturation) + 10%),
    calc(var(--base-lightness) - 5%),
  )
}
Code language: CSS (css)

We can use JavaScript to dynamically update our theme colors based on a site visitor’s preference:

/* Use this purple they chose as our base color */
const docElStyles = document.documentElement.style;
docElStyles.setProperty('--hue', '260');
docElStyles.setProperty('--base-saturation', '90%');
docElStyles.setProperty('--base-lightness', '65%');
Code language: JavaScript (javascript)

We can even use JavaScript to dynamically generate random colors. (The awesome George Francis generated colors for blobs and other characters using this trick. He goes into more detail about why he uses HSL in his generative SVG starter kit)

const hue = Math.random() * 360;
const saturation = 75 + (Math.random() * 20);
const lightness = 75 + (Math.random() * 20);
const color = `hsl(${hue},${saturation}%,${lightness}%)`;
Code language: JavaScript (javascript)

I’m really excited about the opportunities HSL colors unlock. They give designers and developers super powers. They allow computers and humans to communicate about colors in a way humans can understand.

Try it out on your next project. I think you’ll like it.

Comments

NICHOLAS Ivan Seigal said:

You had me convinced from the beginning, but when I got to the side-by-side test I was much faster in RGB, to my shock! I was also shocked to not be able to get either side to past 99.8% matched in a reasonable amount of time. My impression was that HSL started out feeling easier, but was harder to get close to 100% (99.8%) while the RGB was harder initially but converged to 99.7% comparatively easily. I think the reason for this is I have been a computer guy for a long time and less of a digital graphic artist. The each dimension of RGB is nicely conceptually independant too while HSL has the problem, for me at least, that H and L make perfect sense and S is harder to conceptualize since it is relative to L (for example, it might represent a white-to-blue, grey-to-blue, or black-to-blue spectrum). The way to understand HSL is just like paint though, that is true. Start with a light, neutral, or dark base (L) with no tint (H and S). Add tint (H), how much tint is added determines the color. My hours at the paint store remind me of another problem. The HSL scheme also suffers from the fact that some hues are subjectively darker than others (e.g. 100% Yellow is subjectively lighter than 100% Purple for any Luminosity).