https://www.joshwcomeau.com/css/color-formats/
JoshWComeau
* Latest
* Posts
* Snippets
* Goodies
HomeTutorialsCSS
Color Formats in CSS
Table of Contents
IntroductionNamed colorsRGBHex codesHSLModern color formatsDisplay P3
LCHPicking the right color formatSuperpowered design tokensThe
adventure continues
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.
Link to this heading
Named colors
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
Format code using Prettier
Reset code
HTMLCSS
[
Hello world!
Result
Refresh results pane
[ ]
Enable 'tab' key
Developer Anthony Lieuallen created this neat demo, showing all 140
named web colors in a circle:
Created by Anthony Lieuallen. MIT Licensed. View source on Github.
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!
It's a hodgepodge.
So, here's something a bit curious. Check out these two named colors:
gray
darkgray
"darkgray" is actually a lighter color than "gray"??
This is because the 140 named web colors are sourced from different
places, including the HTML4 spec, the X11 Unix windowing system, and
a heartbreaking memorial. It's a hodgepodge of different palettes,
and so it isn't always super consistent.
Link to this heading
RGB
Alright, this is our first "real" color format. Here's how we use it:
Code Playground
Format code using Prettier
Reset code
HTMLCSS
[
Hello world!
Result
Refresh results pane
[ ]
Enable 'tab' key
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:
Red
255
Green
0
Blue
0
rgb(255 0 0);
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
Format code using Prettier
Reset code
HTMLCSS
[
Result
Refresh results pane
[ ]
Enable 'tab' key
The alpha channel ranges from 0 (fully invisible) to 1 (fully
opaque). Anything in-between produces a translucent color.
What is this newfangled syntax??
For most of CSS' existence, we specified RGB colors using a slightly
different syntax.
For opaque colors, we had commas separating each channel:
css
For translucent colors, we used a separate rgba function:
css
This changed in CSS Colors level 4, which introduces a standardized
notation used across newer color formats. rgba() isn't explicitly
deprecated, but it's recommended to use the newer format
(fortunately, browser support is excellent).
As we saw above, the new way of specifying transparency is like this:
css
What's the deal with the slash? This is meant to be a delimiter. It
indicates that the first 3 values are part of a group, and the last
value is something different. It has nothing to do with division.
Link to this heading
Hex codes
This is probably the most commonly-used color format on the web. It
looks like this:
Code Playground
Format code using Prettier
Reset code
HTMLCSS
[
Hello world!
Result
Refresh results pane
[ ]
Enable 'tab' key
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:
#
FF
00
00
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 x 10). With hexadecimal, the total number is 256 (16 x
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
Format code using Prettier
Reset code
HTMLCSS
[
Result
Refresh results pane
[ ]
Enable 'tab' key
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.
Link to this heading
HSL
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 and 360deg 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
Format code using Prettier
Reset code
HTMLCSS
[
Result
Refresh results pane
[ ]
Enable 'tab' key
HSL vs. HSB
Frustratingly, most graphic design software uses a closely-related
color format called HSB. Hue-Saturation-Brightness instead of
Hue-Saturation-Lightness.
Here's how the same color is represented between HSB and HSL, using
Figma's color picker:
[hsb-color-picker][hsl-color-picker]
In HSL, "lightness" is a scale between black and white. 0% lightness
is always black, 100% lightness is always white. If we want a bright,
vivid, saturated color, we should lock our lightness at 50%, halfway
between black and white.
In HSB, things are a bit more complicated. "Brightness" is still a
measure of lightness, and 0% will still produce black, but 100% no
longer always produces white. It depends on the saturation: at full
brightness, 0% saturation is white, and 100% saturation is our
bright, vivid color.
Honestly, I kinda like the HSB model, but it's not an option in CSS.
We only have hsl(), not hsb().
If you ever wind up getting an HSB color value from a designer, a
quick google search turns up lots of online tools you can use to
convert HSB to HSL.
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.
Link to this heading
Display P3
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:
[red-p3]
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:
[]3 squares showing how the P3 color space extends sRGB. Red is
extended by a moderate amount, blue is extended by a little bit, and
green is extended by a ton.3 squares showing how the P3 color space
extends sRGB. Red is extended by a moderate amount, blue is extended
by a little bit, and green is extended by a ton.
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:
css
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.
Color formats and images
When we save images in software like Photoshop, we can choose the
color space that the image uses, allowing us to "embed" the P3 color
space inside an image. When we display that image in browsers like
Chrome, we'll see the more-vibrant colors even though the CSS color()
function isn't supported!*
This can be a handy way to sneak some brighter colors into your
projects today, while we wait for browsers to catch up.
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!
Link to this heading
LCH
Let's consider these two colors, created using the HSL color format:
hsl(60deg 100% 50%)
hsl(240deg 100% 50%)
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 "lightness" value should feel equally
light!
For example, here's what yellow and blue look like, at 55% lightness,
in LCH:
lch(55% 132 95)
lch(55% 132 280)
Here's an LCH color picker. Spend a moment or two experimenting with
it, to get a feel for how it works:
Chroma
Lightness
Hue
0 degrees
[.box { background-]
.box {
background-color: lch(50% 100 0);
}
LCH stands for "Lightness Chroma Hue". "Chroma" is more-or-less a
synonym of "saturation"*. It's conceptually very similar to HSL, but
with two big differences:
1. As noted, it prioritizes human perception, so that two colors
that share the same "lightness" value will feel equally light.
2. It isn't bound to any particular color space.
Unlike the other color formats we've seen, LCH isn't bound to sRGB.
It isn't even bound to P3! It achieves this by not having an upper
limit on Chroma.
In HSL, saturation ranges from 0% (no saturation) to 100% (fully
saturated). This is possible because we know that we're talking about
the sRGB color space, a finite palette of colors.
But LCH isn't linked to a particular color space, and so we don't
know where the upper saturation limit is. It's not static: as display
technology continues to improve, we can expect monitors to reach
wider and wider gamuts. LCH will automatically be able to reference
these expanded colors by cranking up the chroma. Talk about
future-proofing!
css
Now, before you get too excited: LCH is currently only supported in
Safari (though they're working on it in Chrome!). I think it'll be a
few years until this color format can safely be used.
If you'd like to learn more about LCH, check out Lea Verou's seminal
blog post. I owe her and Chris Lilley a big thanks; their LCH color
picker helped me build mine!
My flawed color picker
I should also tell you: the LCH color picker I built is riddled with
inaccuracies . It's locked to the sRGB space (even if you're viewing
this on Safari), and the displayed values for lightness/chroma are
approximate.
Based on the formulas I've found, lightness should be capped from 0
(black) to 100 (white), but this doesn't seem to be the case in
Safari, where LCH is actually implemented. I'm guessing that this is
a recent revision to the format, to help make sure that two colors at
the same lightness/chroma feel equally saturated.
All of this to say: my color picker is intended to give you a broad
sense of the format, but you shouldn't trust it too much!
oklch
I recently learned of another color format: oklch(). It's nearly
identical to lch(), but fixes a bug related to how blue hues shift as
the chroma/lightness is adjusted.
Assuming that browsers adopt both options simultaneously, oklch seems
like a safer choice.
You can learn more about the issue in a wonderful article from Evil
Martians.
Link to this heading
Picking the right color format
So, we've completed our tour, and seen lots of different color
formats*. Which one should you actually use in your work?
Personally, I recommend using HSL. At least until LCH gains
widespread browser support.
I know a lot of devs like using hex codes. They're terse, easy to
copy/paste between design software and our code, and universal. But
those benefits come with some pretty hefty tradeoffs.
Even if you're comfortable with hexadecimal notation, it's still
pretty tough to decipher. Quick: What color is #0F52B7? How about #
F3E248??
The wonderful thing about HSL is that it's intuitive. It aligns
closely with how we tend to think about color. With a bit of
practice, you'll be able to immediately picture any color when you
see the hsl() value.
And that means we can easily tweak values, on the fly. I don't even
need to open a color picker. If I want my color to be a bit darker, I
can decrement the lightness percentage. If I want it to be more vivid
and intense, I can crank up the saturation.
HSL gets even more powerful when we combine it with modern CSS
features.
Link to this heading
Superpowered design tokens
Years ago, I used to use a CSS preprocessor called Sass. One of the
best things about Sass was that it came with color-manipulation
functions:
css
Here's the really cool thing: When we use HSL, we can manipulate
color like this in vanilla CSS!
We'll need to leverage CSS variables to help:
Code Playground
Format code using Prettier
Reset code
HTML
[
This text is red.
This text is darker.
This text is transparent.
This text is softer.
CSS
[html { --red-hue: ]
html {
--red-hue: 0deg;
--red-sat: 100%;
--red-lit: 50%;
--red: hsl(
var(--red-hue)
var(--red-sat)
var(--red-lit)
);
--dark-red: hsl(
var(--red-hue)
var(--red-sat)
calc(var(--red-lit) - 20%)
);
--transparent-red: hsl(
var(--red-hue)
var(--red-sat)
var(--red-lit) / 0.5
);
--soft-red: hsl(
var(--red-hue)
calc(var(--red-sat) - 30%)
calc(var(--red-lit) + 10%)
);
}
Result
Refresh results pane
[ ]
Enable 'tab' key
To explain what's going on here: We have little "color fragments"
stored in CSS variables, and we're using them like LEGO(tm) bricks,
assembling them into fully-formed colors.
The calc() function lets us modify those fragments. For example,
considering the --dark-red color:
css
We're using the standard hue and saturation for our red color, but
we're lowering the lightness by 20%. The color goes from hsl(0deg
100% 50%) to hsl(0deg 100% 30%).
Now, this might seem a heck of a lot more complicated than the Sass
way. It's definitely more typing. But let's not lose sight of the
fact that this is all happening in vanilla CSS.
Unlike with Sass variables/functions, which compile away into
hardcoded values, CSS variables are dynamic. We can tweak any of
these values using JavaScript, and all the other ones will
automatically update.
This is super handy for things like adding a toggleable dark mode,
user-defined color themes, and more.
There are so many cool things we can do when we combine an intuitive
color format like HSL or LCH with the modular power of CSS variables
and calc. I feel like we've only seen the tip of the iceberg, and I
hope this is an area that we continue to explore and experiment with!
Link to this heading
The adventure continues
So I have a question for you: How much do you enjoy writing CSS?
Personally, I enjoy it quite a bit. I suspect that much is obvious,
from this article . But this wasn't always the case!
I started tinkering with CSS back in 2007, and for about a decade, I
stumbled my way through it. I got things done, but I didn't really
have much confidence. Things felt precarious, like a house of cards.
I'd get into these funky situations where the UI wasn't doing what I
wanted, and so I'd throw random properties and values. The CSS was a
hot mess, but at least the UI looked mostly correct?
CSS is a surprisingly difficult language to master. No matter how
many years I spent practicing, I always felt like there was so much I
didn't know. And so I decided to fix it.
I spent years proactively trying to understand CSS. When the language
surprised me, I'd settle into the problem like a warm bath and really
dig into it, searching the MDN documentation, the CSSWG
specifications, and doing a lot of experimentation, building out my
mental model one brick at a time.
This was not a quick or easy process, but by golly it was effective.
Things started making so much more sense to me. I kept having
epiphanies, like "ohhh that's why this is happening!". Puzzle pieces
kept snapping into place, and soon I had a clear picture of what was
happening.
I want to help accelerate this process for you. I created a
self-paced, comprehensive online course called CSS for JavaScript
Developers.
[css-for-js]
Over the course of 10 modules, we'll go through the language
comprehensively, learning about Flexbox, CSS Grid, positioned layout,
flow layout, and more. We'll build a robust mental model, letting you
leverage your intuition to solve challenging problems. And we'll
cover a ton of modern CSS features (we learn more about CSS variables
and calc in the course!).
The course uses the same tech stack as this blog post, and so it's
jam-packed with interactive widgets and code playgrounds, but it's so
much more. There are 170+ short videos, tons of exercises,
real-world-inspired projects, and even a few mini-games.
It's specifically built for folks who use a JS framework like React/
Angular/Vue. 80% of the course focuses on CSS fundamentals, but we
also see how to integrate those fundamentals into a modern JS
application, how to structure our CSS, stuff like that.
If you struggle with CSS, I hope you'll check it out. Gaining
confidence with CSS is game-changing, especially if you're already
comfortable with HTML and JS. When you complete the holy trinity, it
becomes so much easier to stay in flow, to truly enjoy developing web
applications.
Learn more at https://css-for-js.dev/.
---------------------------------------------------------------------
Thanks for reading, and have fun experimenting with new color
formats!
[?]
Last Updated
December 15th, 2022
Hits
A front-end web development newsletter that sparks joy
My goal with this blog is to create helpful content for front-end web
devs, and my newsletter is no different! I'll let you know when I
publish new content, and I'll even share exclusive newsletter-only
content now and then.
No spam, unsubscribe at any time.
First Name
[ ]
Email
[ ]
Do you agree to the terms?
If you're a human, please ignore this field.
[ ]
Subscribe
JoshWComeau
Thanks for reading!
(c) 2020-present Joshua Comeau. All Rights Reserved.
Tutorials
ReactAnimationCSSCareerGatsbyNext.jsPerformanceJavaScript
Links
TwitterContactTerms of UsePrivacy Policy
(c) 2020-present Joshua Comeau. All Rights Reserved.