I was just writing in my “What’s new in since CSS3?” article about recent and possible future changes to CSS colors. It’s weirdly a lot. There are just as many or more new and upcoming ways to define colors than what we have now. I thought we’d take a really quick look.
First, a major heads up. This stuff is so complicated. I barely understand it. But here are some aspects:
- Before all this upcoming change, we only had RGB as a color model, and everything dealt with that.
- We had different “color spaces” that handled it differently (e.g. the
rgb()
function mapped that RGB color model as a cube with linear coordinates, thehsl()
function mapped that RGB color model as a cylinder) but it was all sRGB gamut. - With the upcoming changes, we’re getting new color models and (!) we’re getting new functions that map that color model differently. So I think it’s kind of a double-triple whammy.
I can’t personally educate you on all the nitty-gritty details — I’m writing this because I bet there are a lot of you like me, wondering why you should care at all about this, and this is my attempt to understand why I should care about all of it.
Display-P3 is one that opens up a ton of more vibrant color that was able to be expressed before.
body {
background: color(display-p3 1 0.08 0); /* super red! */
}
It turns out that modern monitors can display way more colors, particularly extra vibrant ones, but we just have no way of defining those colors with classic CSS color syntaxes, like HEX, RGB, and HSL. Super weird, right?! But if you use Display-P3, you get a wider range of access to these vibrant colors.
![Screenshot of a super bright pink in a CodePen preview using the display-p3 CSS color syntax.](https://i0.wp.com/css-tricks.com/wp-content/uploads/2022/02/CleanShot-2022-02-02-at-07.02.33@2x.png?resize=2310%2C2300&ssl=1)
The dev shop Panic latched onto this early on and started using these colors as a “secret weapon”:
🌈 Along with WebGL, p3 colors are now a big part of the Panic website secret weapon pile. Shh, don’t tell anyone, but you should see this page on an iMac Pro screen! https://t.co/glrhPNuCdR
— Panic (@panic) May 24, 2019
Jen Simmons also covers how to use them, including a fallback for non-supporting browsers:
Display P3 color. Designing in the browser. Amazing.
— Jen Simmons (@jensimmons) January 5, 2022
Let me show you how to switch over to P3, find a color, and then find a fallback color for older browsers. All while working inside Safari Web Inspector. (Turn sound on to hear me explain!) pic.twitter.com/AaKhrn2s3e
Resources
- Wide Gamut Color in CSS with Display-P3 (WebKit Blog)
- The Expanding Gamut of Color on the Web (Ollie Williams)
HWB is the one that is more “for humans” except that’s a bit debatable and it’s still based on sRGB.
I had no idea hwb()
was a thing — shout out to Stefan Judis for blogging about it.
I normally think of HSL as the CSS color format that is “for humans” (and good for programmatic control) because, well, manipulating 360° of Hue and 0-100% of both Saturation and Lightness make some kind of obvious sense.
But in hwb()
, we’ve got Hue (the same as HSL, I think), then Whiteness and Blackness. Stefan:
Adding White and Black to a color affects its saturation. Suppose you add the same amount of White and Black to a color, the color tone stays the same, but color loses saturation. This works up to 50% White and 50% Black (
hwb(0deg 50% 50%
)), which results in an achromatic color.
![Showing six gradients going from red to black and the impact that change CSS color values in hwb has on the transition between colors.](https://i0.wp.com/css-tricks.com/wp-content/uploads/2022/02/hwb.png?resize=1100%2C545&ssl=1)
Stefan expressed some doubt that this is any easier to understand than HSL, and I tend to agree. I probably just need to get more used to it, but it seems to be more abstract than simply changing the lightness or saturation.
HWB is limited to the same color gamut (sRGB) as all the old color formats all. No new colors are unlocked here.
Resources
- HWB Color Picker
- hwb() – a color notation for humans? (Stefan Judis)
rgb()
of a much wider gamut
LAB is like div {
background: lab(150% -400 400);
}
I liked Eric Portis’ explanation of LAB when I went around asking about it:
LAB is like RGB in that there are three linear components. Lower numbers mean less of the thing, bigger numbers mean more of the thing. So you could use LAB to specify the brightest, greenest green that ever bright-greened, and it’ll be super bright and green for everybody, but brighter and greener on monitors with wider gamuts.
So, we get all the extra color, which is awesome, but sRGB had this other problem (aside from being limited in color expression), that it isn’t perceptually uniform. Brian Kardell:
The sRGB space is not perceptually uniform. The same mathematical movement has different degrees of perceived effect depending on where you are at in the color space. If you want to read a designer’s experience with this, here’s an interesting example which does a good job struggling to do well.
The classic example here is how, in HSL, colors with the exact same “Lightness” really don’t feel the same at all.
HSL vs LAB:: lightness 💡
— Adam Argyle (@argyleink) December 3, 2019
Same colors from our tricky color poll, but this time I’ve shown LAB’s version of the same color over top. Notice how much closer LAB’s lightness value is to the results of our poll!
🎨 color spaces aren’t all the same y’all! https://t.co/AIEs0amdWY pic.twitter.com/xkEguq3KZG
But in LAB, apparently, it is perceptually uniform, meaning that programmatically manipulating colors is a much more sane task. And another bonus is that LAB colors are specced as being device-independent. Here’s Michelle Barker:
LAB and LCH are defined in the specification as device-independent colors. LAB is a color space that can be accessed in software like Photoshop and is recommended if you want a color to look the same on-screen as, say, printed on a t-shirt.
Resources
-
lab()
(MDN) - A Guide To Modern CSS Colors With RGB, HSL, HWB, LAB And LCH (Michelle Barker)
hsl()
of a much wider gamut
LCH is like Remember how I said HSL is “for humans” in that it is easier understand than RGB? Changing the Hue, Saturation, and Lightness makes a lot of logical sense. Similar here with lch()
where we’ve got Lightness, Chroma, and Hue. Back to my conversation with Eric Portis:
LCH is more like HSL: a polar space. H = hue = a circle. So doing math to pick complementary colors (or whatever transforms you’re after) becomes trivial (just add 180 — or whatever!)
I suppose you’d pick LCH just because you like the syntax of it or because it makes some complicated programmatic thing you’re trying to do easier — and you get the fact that it can express 50% more colors for free.
We get the perceptual uniformity here, too. Here’s Lea Verou who seems excited that lightness will actually mean something:
In HSL, lightness is meaningless. Colors can have the same lightness value, with wildly different perceptual lightness. […] With LCH, any colors with the same lightness are equally perceptually light, and any colors with the same chroma are equally perceptually saturated.
Another benefit of the new model is that we can wipe our hands clean of the “gray dead zone” in CSS color gradients. I think because of this perceptual uniformity stuff, two rich colors won’t get cheeky and gradient themselves through non-rich territory.
![Two gradients going from blue to pink, one on top of the other. The first uses the LCH CSS color syntax and the second use HSL. HSL has noticeable gray areas.](https://i0.wp.com/css-tricks.com/wp-content/uploads/2022/02/6-modern-css-color-1.webp?resize=2000%2C772&ssl=1)
Here’s a small personal prediction: I’d say that lch()
is probably going to be a designer favorite. Soon there are going to be a ton of new color choices and it’s too difficult and weird to always be picking different ones. LCH seems to have the most bang for the mental buck.
Resources
lch()
(MDN)- LCH colors in CSS: what, why, and how? (Lea Verou)
- LCH Colour Picker
“OK”
LAB ‘n’ friends seems so new because it is new… to CSS. But LAB was invented in the 1940s. In a conversation with Adam Argyle, he used a memorable phrase: All the color spaces have an Achilles’ heel. That is, something they kinda suck at. For sRGB, it’s the grey dead zone thing, as well as the limited color gamut. LAB is great and all, but it certainly has its own weaknesses. For example, a blue-to-white gradient in LAB travels pretty awkwardly through purpletown.
In December 2020, Björn Ottosson is all like “Hey, a new color space just dropped,” and now OKLAB exists. Apparently the CSS powers-that-be see enough value in that color space that both oklab()
and oklch()
are already specced. I guess we should care because they are just generally better, but don’t quote me on that.
color()
function but the other’s don’t?
Why is it Display P3 uses the I don’t really know. I think the CSS color()
function is a bit newer and that’s just how Safari dunked it in there to start. I have no idea if Display P3 will get its own dedicated function, or if we all should just start using CSS color()
, or what.
/* This is how you use Display P3 */
color(display-p3 1 0.08 0);
/* But this doesn't work */
color(oklch 42.1% 0.192 328.6);
/* You gotta do this instead 🤷♀️ */
oklch(42.1% 0.192 328.6);
/* But you can use the color space within a gradient... */
background-image: linear-gradient(
to right
in oklch,
lch(50% 100 100),
lch(50% 100 250)
);
The relative color syntax is super useful.
There is this really cool ability called “relative color syntax” where you can basically deconstruct a CSS color while moving it into another format. Say you have the (obviously) most famous CSS HEX color ever, fog dog, and you wanna kick it into HSL instead:
body {
background: hsl(from #f06d06 h s l);
}
Maybe that’s not all that useful immediately, but hey, now we’re able to add alpha to it! There is literally no other way to apply alpha to an existing HEX color, so that’s kinda huge:
body {
background: hsl(from #f06d06 h s l / 0.5);
}
But I can also mess with it. Say I wanna saturate fog dog a bit before I add opacity because the lower opacity will naturally dull it out and I wanna combat that. I can use calc()
on the implied variables there:
body {
background: hsl(from #f06d06 h calc(s + 20%) l / 0.5);
}
That’s so cool. I’m sure we’ll see some amazing things come from this. And it certainly isn’t limited to HSL. I was just using HSL because it’s what is comfortable to me right now. I could start with the named color red
and mess with it in LCH if I want:
body {
background: lch(from red l calc(c + 15) h / 0.25);
}
This stuff is going to be most useful when liberally combined with custom properties.
There are no special functions just for alpha anymore.
Just to be clear: no commas preceding the alpha value in a CSS color function — just a forward slash instead:
/* Old! */
rgb(255, 0, 0);
rgba(255, 0, 0, 0.5);
/* New! */
rgb(255 0 0);
rgb(255 0 0 / 0.5);
hsl(0deg 40% 40%)
hsl(0deg 40% 40% / 90%) /* can be percentage rather than 0.9 or whatever */
/* The New color stuff ONLY has the single base function, no alpha secondardy function */
lab(49% 39 80)
lab(49% 39 80 / 0.25)
/* Display P3, with the color function, essentially works the same way with the slash */
color(display-p3 1 0.08 0 / 0.25);
You can even define your own CSS color space.
But I literally can’t even think about that. It blows my mind, sorry.
Personally I am extremely excited for
lch()
and would start using it as soon it gets wider browser support. Also,oklab()
andoklch
are not only specced but they are also supported in Safari sinceTP 137
.And.
very informative, thx
“There is literally no other way to apply alpha to an existing HEX color…”
I think we’ve been able to use an implied 4th channel for a long time:
#f06d0688 will give you the colour with a transparency. With 00 being fully transparent, and ff being fully opaque.
wow this is crazy
Yes, using the four-byte hex has been around for a while. But there has been no way to add alpha to a declared hex color in, say, a custom property before the new relative syntax. You could with the rgb/hsl color functions and custom properties.
That confused me too. I’ve been using RGBa for aeons. Am I misinterpreting something?
Also note that you can use shorthand, e.g. #fc08 (rgba), which comes out like #ffcc0088 (rrggbbaa)
I discovered this accidentally. I was frustrated and wanted a transparency without having to convert HEX to rgb and add the A so I just added 44 at the end of my hex and Bam! transparency! Quickly, I discovered it works with short code too! #fff8 works the same as #ffffff88. I don’t know when this began, but I have been using it ever since!
I think it would be awesome to have an article explaining why we need display-p3, and why we can’t express all colors with RGB anymore.
Why do we have color profiles, and why can’t we all just expect #FFF to be the whitest white, and #F00 to be the reddest red, etc.
“Stefan expressed some doubt that this is any easier to understand than HSL, and I tend to agree. I probably just need to get more used to it, but it seems to be more abstract than simply changing the lightness or saturation.”
It might help to think of HWB as if you were a child playing with tempera colors.
Which color do you like? Pick a color tube (H).
Do you want it lighter? Add more white (W).
Do you want it darker? Add more black (B).
Did you mix white and black together in equal parts? Well, now you have to paint your cute rabbit grey, I’m sorry.
Compared to HSL, I feel HWB is actually more concrete, and thus easier to grasp.
Seems like color attribute requires a standard colour profile that defines a colour space. Having srgb as an attribute confuses things but it is grandathered in.
Hsl, rgb, lab… those are methods to describe a point in a colour space not the profile in itself.