Introduction
CSS has a whole slew of different color formats: hex codes, rgb(), hsl(), lch(), the list goes on!
Which one should we use? It might seem like an inconsequential decision, but there are some pretty important differences between them. And, honestly, I think most of us are prioritizing the wrong things. 😅
In this tutorial, I’ll take you on a tour of the different options. We’ll see how they work, how we can decipher them, and how we can use them to our advantage. Later, I’ll show you how modern CSS lets us make on-the-fly adjustments, if we pick the right color format.
So, this isn’t really a color format, but it’s a good place to start!
HTML comes with 140 named colors. These are special keyword values like dodgerblue
, hotpink
, and tomato
:
Code Playground
Developer Anthony Lieuallen created this neat demo, showing all 140 named web colors in a circle:
Named colors are great when you need a placeholder color. For example, if you’re building a prototype and need temporary values, or if you’re writing educational content. In terms of readability, nothing beats color: red
.
It probably goes without saying, but we generally don’t use named colors in production applications. 140 colors just isn’t enough—it’s even less than the 8-bit color palette available on the original NES console!
Alright, this is our first “real” color format. Here’s how we use it:
Code Playground
Like most color formats, rgb
is an acronym. It stands for red green blue.
Of all the color formats we’ll learn about today, rgb
is the least abstracted. Your computer/phone display is really just a collection of millions of tiny red, green, and blue LEDs, assembled into pixels. And so, the rgb
color format lets us tweak the brightness of those lights directly.
Each value — red, green, blue — is referred to as a channel. Each channel goes from 0 to 255. By mixing these channels in different amounts, we can create over 16 milion different colors.
Here’s an rgb
color picker. Spend a couple moments getting a feel for how it works:
The neat thing about RGB color is that it’s based on the physics of light. We can mix red, green, and blue light together to create any color. Crank them all to 255, and we get white. Set them all to 0, and we’re left with black.
The rgb
color format also allows us to specify a 4th optional value for the alpha channel, to control transparency:
Code Playground
The alpha channel ranges from 0
(fully invisible) to 1
(fully opaque). Anything in-between produces a translucent color.
This is probably the most commonly-used color format on the web. It looks like this:
Code Playground
Here’s how it works: a 6-digit hex code contains three 2-digit values, one for each channel (red / green / blue). Instead of using a 10-digit decimal system, it uses a 16-digit hexadecimal system.
This’ll be clearer with an interactive demo. Try dragging the sliders to discover how hex codes work:
Fundamentally, hex codes are the same as RGB values. In both cases, we’re providing a value for red, green, and blue.
In a decimal system, a two-digit value can contain 100 possible values (10 × 10). With hexadecimal, the total number is 256 (16 × 16). And so it really is just like rgb()
, where we’re specifying a value between 0 and 255 for each R/G/B channel.
And here’s a fun fact: we can pass an eight digit hex code if we want to include an alpha channel:
Code Playground
In this example, we’re specifying 80
as the alpha channel, which is equivalent to 128 in a decimal system. As a result, this box is 50% opaque.
8-digit hex codes are widely implemented in modern browsers, with 96% global support. Sadly, they aren’t supported in IE.
So far, both of the color formats we’ve seen are different “wrappers” on the same fundamental idea: passing specific values for red/green/blue channels.
This isn’t the only way to think about color, though! Let’s look at a totally different color format: HSL.
Let’s start with the color picker this time:
This color picker probably feels much more familiar. It’s similar to the ones used in graphic design software like Figma or Photoshop.
This color format takes 3 different values:
-
Hue: This is the pigment we want to use. Valid values range from 0 to 360, and we specify it in degrees because the scale is circular (
0deg
and360deg
represent the same red hue). -
Saturation: How much pigment is in the color? Valid values range from 0% to 100%. At 0%, there is no pigment in the color, and it’s totally grayscale. At 100%, the color is as vibrant as possible.
-
Lightness: how light/dark is the color? Valid values range from 0% to 100%. At 0%, the color is pitch black. At 100%, the color is pure white.
This tends to be a really intuitive way to think about color. Instead of controlling the R/G/B light values directly, we’ve moved to a higher level of abstraction, one more closely aligned with how humans typically think about color.
Like we saw with RGB, we can specify transparency with the /
delimiter:
Code Playground
Link to this heading
Modern color formats
So, all of the color formats we’ve seen so far have been around for many, many years. HSL was even supported way back in Internet Explorer 9 (released in 2011)!
Recently, however, we’ve been getting some new color formats in CSS. They’re pretty compelling. Let’s talk about them.
So, this blog post is about color formats, the syntaxes we use to specify colors. All of the true color formats we’ve seen so far — rgb()
, hex codes, and hsl()
— are all bound by the “standard RGB color space”, commonly abbreviated as sRGB.
A color space is a collection of available colors, the palettes we have to pick from. There are millions of possible colors in sRGB, but it doesn’t come close to capturing the full range of colors the human eye is capable of seeing.
Take a look at the following two red squares:
On the left, the color is rgb(255 0 0)
. It’s the reddest red possible in the sRGB color space. On the right, however, I’m using the P3 color space. It’s an even redder red!
(There’s a good chance these two squares look identical to you. If so, it likely means that your monitor or browser doesn’t support wide-gamut color formats. You might have better luck checking on your mobile device! iPhones in particular have supported wide-gamut color for a few years now.)
P3 extends the standard sRGB color space, giving us access to brighter and more vibrant colors. I really like this image, from a wonderful WebKit blog post:
Unfortunately in CSS, the color space is linked to the color format. If I choose to use the rgb()
syntax (or hex codes, or hsl()
), I can only ever specify colors in the sRGB color space.
So, if we want to use the P3 color space, we need to use a different color format. Here’s the syntax:
The color()
function takes a color space, and then a set of R/G/B values. Instead of ranging from 0 to 255, it uses decimal values from 0 to 1.
Browser support for the color()
function is not good. As I write this, it’s only implemented in Safari.
Ultimately, it’s exciting to gain access to a wider palette of colors, but I don’t love specifying color using R/G/B channels. Fortunately, it’s not the only new kid on the block! 😄
Let’s consider these two colors, created using the HSL color format:
As we can see, both of these colors have the same “lightness” value of 50%. They don’t feel equally light, though, do they? The yellow feels way lighter than the blue!
The HSL color format is modeled after math/physics. It doesn’t take human perception into account. And, it turns out, humans don’t perceive colors very accurately!
LCH is a color format that aims to be perceptually uniform to humans. Two colors with an equivalent