Skip to main content Go to the homepage
State of the Browser

Color in CSS:
How I learned to disrespect Tennis.

Everybody's talking about container queries, nesting, scroll-driven animation, and view transitions. In all the excitement for these new modules, one topic is a bit overlooked: color in CSS. Manuel Matuzović summarizes all the new things we can do in CSS with color to create flexible, scalable, accessible, and user-friendly design systems.

Oh, and he talks about squash…

Links
Transcript

That was the song Squash Man. My name is Manuel and today I'm here to talk about color in CSS. And the reason I picked this topic, Color in CSS, I got really excited about color recently. I wrote, it surprises me how excited I am by color theory. I find it hard to understand and learn, but super interesting, I can see myself color on the web next year. And that's what I did.

And to be honest, before that, I never really cared about color in general, and also, color in CSS. But I was working on a new workshop called the advanced CSS master class. And in preparation for this workshop, I made a list of all of the different properties and modules and terms I wanted to teach people.

And if you look at the list, you'll see there are many things that most people are really excited about. Like container queries, nesting, scroll driven animations, and my beloved class, which is one of the most significant additions to CSS, if you ask me. And looking at this list, I also realized many of the topics were related to color.

And that made me curious because these are the only recent additions. And I was wondering, what else we can do in CSS with color. So, I went back 5 years, ten years, even 20 years and as it turns out, there's actually a lot you can do with color in CSS. And you know, of course, it makes sense. CSS is constantly evolving. And in 20 years, stuff happens. When I had this list in front of me, I was surprised how much there is and how little I was using.

My goal today is to get you as excited about color in CSS as I am and also, show you some of the new stuff that we can do. And to do that, we have to go back 25 years, don't worry, I'm not going to take you down memory lane for the next 15 minutes, but we have to start where I built my very first website. Before I show you the next slide, does anyone in this room speak French?

OK. I'm sorry.

[ Laughter ]

So -- 25 years ago, I played a game called Counterstrike, this online I played with my friends. We had a group so-called clan, and for the clan, I made a website and that's what you see here. It's three column layout. There's a background color and white text in the first column, there's navigation with some industrial style metallic looking inaccessible image buttons.

And in the right column is some secondary content, and the middle column is the main content. And at the very top, it says (French), the thing is, I was 13 years old, we played this game and we started learning French in school so of course, that was the name of our clan.

What's special about the website, except for the name and the fact it was optimized for a certain resolution, is that there is no CSS on these sites. So of course, for the layout, I'm using highly inaccessible tables, table layout.

And for styling, I'm using presentational attributes, like BG color text and A link and so on.

And what's special about this code you see here, first of all, I'm using hex codes, and second of all, I'm using specific hex codes. Because back then, I was told I could only use a subset of all the 16.7 million colors that RGB offered. The so-called web safe. There were only 216 colors I could use because back then, screens couldn't display more than 256 colors. You wanted to make sure your colors would be displayed correctly, you would to pick one of these. It's 216 not 256, because there were practical reasons that doesn't matter right now.

All right. So, yeah, then my next -- or my first big milestone was in 2006 when I finally learned CSS and I was still doing the same things. I was applying hex values to properties in CSS now instead of HTML.

And thankfully, the 256 color problem was gone because screens got better and now, we could use all of the colors in RGB. And another milestone was gradients. Before we had gradient functions, if you wanted a gradient, you would have to open photoshop, draw ha gradient, save that as an image.

And then, in your CSS, you find the element that you want to style, you reference the image, you make sure that you repeat it horizontally. And then, you check, and then, you need to apply some CSS pro skills to fill the entire screen. And that's it, you've got yourself a gradient. Similar to box shadow technique. It's fine, it worked. But of course, that was the big milestone for me, the linear gradient function in CSS.

That's it. At least for me for the first 15 years or so of me writing CSS. I didn't do much more with color. That's it. Apply hex values and draw a gradient here and there. The only other thing I was using was the RGB function, the RGBA function, as we heard, it has this convenient feature of being able to add an alpha channel.

But honestly, I never understood how RGBA is better than hex, because I find both essentially unreadable. They're very developer unfriendly. For example, can you tell me which color this hex code represents? Is RGB better?

It's the color you see, the color of the text, this dark pink whatever it is. I don't know. I was in Hamburg last year for the Hamburg meetup, shout out to Leah and my friend mark who told me there is a color blind design who can actually read hex codes and tell you the color they represent, even though they are not able to perceive the color. There's a talk from .CSS in 2018. Check it out if you want to.

What I like much better than hex is HSL because HSL is developer friendly. It's readable. HSL takes free values, the first one is the hue, you define it by picking a value between zero degrees and 360 degrees and then, you have to saturation, defined in percentages, from 0% to 100% and the lightness from 0% to 100%. And 0% means black and 100%, it's white.

And if you know how the color wheel looks like, zero degrees with red and then, slowly turns yellow at 60 degrees, green at 120, and magenta, and cyan and so on, if you know that, you can read HSL. If you look at that code, you know it's a green color at 120 degrees, it's highly saturated color, it's intense, vivid, because 100% saturated and it's a dark color because it only has 10% lightness. It's a dark intense green.

Here's another representation of HSLs, there's a cone shape in the circle we pick the inside out, define the saturation and going up and down, we define the lightness. And HSL is especially great if you're working with custom properties in CSS.

So let's take this notification component, or as I like to call it diff that I built. Custom property for the background color and one for the border color and applying it for the respective properties. This code is fine, right?

But it can be improved because as you can see, the hue is the same, the saturation is the same and only the lightness is a bit different. I create three more custom properties, one for hue, saturation, one for the lightness. We reuse the hue, reuse the saturation and reuse the lightness with the difference that in the border color, custom property, I use the function. And this is really dynamic and flexible and something you can use to build the system.

If you need a variation or multiple variations of the component, all we have to do is change the hue custom property. And then, we have different variations in different colors, which is cool. Sound like HSL is perfect for color systems inside design systems. Except that it isn't.

There's a problem of HSL. It's kind of like tennis. It has some great ideas, great features, but it sucks.

Before I explain why we have to learn two new terms, gamut and color space. And now, it's a good time to tell you I'm not an expert in color theory. Like the things I'm saying today are probably not 100% accurate, but it's good enough for you and me, should be OK.

All right. We start with gamuts. The gamut defines the amount of colors or the range of colors and the gamut can have the name, SRGB, for example, or display P3. What you see here is triangle filled with color is all of the colors that we can access as SRGB.

And we have another shape, this gray shape, which represents the colors we can actually perceive as human beings. We can see the colors that we can use in SRGB, the amount of colors is limited, but way more we can see. And there are different gamuts. Here using triangles, the central one, red one is RGB, and the so on, and you see how pro photo can display way more colors. Here's a different representation because someone told me last week that this slide sucked. So I added another slide.

Here, we can see SRGB, the SRGB gamut in the color space in this rectangular space. And display P3 offers more colors and then even more colors. You can see how much more colors there are in these gamuts.

So, when we talk about the amount of colors or the range of colors, that's when we use the term gamut. We can talk about low range or a narrow gamut or a high range or wide gamut.

The next term we need to know is color spaces. And the color space is an arrangement of a gamut and establishes a method of accessing colors.

For example, there's the RGB color space. This is a little bit confusing because there's an RGB gamut and also, a color space by the same name. And what RGB does, it creates this rectangular shape. And you can access colors using the RG and B channel. That's what we're doing in hex and in the RGB function.

There's another color space, HSL, which also uses the SRGB gamut. But it has a different shape. It has this cylindric shape, and other methods of accessing the colors, hue, saturation, and lightness. We use the term color space to define an arrangement of colors and also methods of accessing colors and also, when we talk about interpolation, which we'll do later today.

So here's another cool demo because it looks professional. Here's a square with some named colors. And then, we can display the same colors in a different color space, like HSL, where if this conic shape.

So coming back to the problem with tennis and HSL. If we take the RGB color space, which is in the square shape and convert it to HSL, we get this conic shape. All right? And as long as we stay inside that shape when we use our combinations of hue saturation and lightness, we're fine.

But if we use a combination that ends up somewhere outside of the shape, we have a problem. Because the thing with HSL, we can use any combination of hue, saturation, and lightness, even if there isn't a color that represents these numbers, and that's a problem.

And a good way of dealing with that would be to find an algorithm that just picks the closest matching color for you automatically. You use a combination, the color doesn't exist, you end up out of gamut, an algorithm recognizes that, and just finds the closest number.

Unfortunately, HSL is pretty old, and back then, they weren't able to come up with a performant algorithm for that. Instead, what they did, they filled the color space white and black. White at the top, and black at the bottom. Now you can't end up without gamut, all hues have the same maximum values, but the problem is that they're artificially creating colors that don't match. Colors that don't reflect human perception of lightness.

They're just artificially mixing whites to colors and black to colors. And this can be a problem. And also, if you look at this circle, this color wheel in HSL, it's not a perfect circle in the representations I've shown you. Because there isn't the same amount of saturation for every hue. And this can be really, really big problem for us in CSS. It makes picking colors really unpredictable.

If you look at this scale, what you can see here is different hues with the same amount of saturation and the same amount of lightness. And something is really, really wrong with that. If you look closely.

Can you see how the blues are really, really dark and the greens are really, really light? So the blue is much darker than the green, which shouldn't be the case because they have the same amount of saturation, the same amount of lightness. Only the hue is different. And that's the problem with HSL.

Yeah. So, according to the column model, this is uniform, but it definitely feels wrong for us as humans. And this is why we say HSL is perceptionally not uniform or ununiform. We have two buttons, actual buttons. One says send, and the other one says -- yeah, very controversial, I know.

One says send and the other one says save, and we want to create a variation for the save button, like a green color, for example. If we go back to the notification component solution and we just, you know, take the same solution, all I would have to do now is change the hue property.

To something close to 120 or so, so we get a green button, and if I do that, we end up with this, which is really unexpected. I didn't change the saturation, didn't change the lightness, only changed the hue, but the green button is much, much brighter than the purple button. And this is where lab comes into play. Lab was designed as a uniform space. Remember when I told you I don't know about color theory. I don't know how they do it, scientists, but they're able to take human perception into account. And what's cool about lab, it also designed to cover entire range of visible colors if the device supports that.

There's a lightness component similar to HSL, defines the term between 0% and 100%, and then, we have A and B. And A, the A is relative to the components green and red, and the B axis is blue and yellow. And it's not just great because it's perceptually uniform, not RGB anymore, but display P3, which means 50% more colors. Remember the triangles I showed you? The purple triangle was larger, it's 50% larger, way, way more colors than we can use.

The only thing I don't like about lab, again, we're mixing colors. We have the lightness component, but you have to mix red with blue, which doesn't work for my brain. Luckily, there is LCH.

So just as HSL is an easier to use representation of RGB, LCH is an easier to use representation.

And also a hue. And yeah you can see it's in the cylindrical shape, but it doesn't artificial little fill it up with black and white. It's just like it is. And that's because instead of artificially creating colors, what lab does is if a color is out of gamut, it tries to find a better alternative that somehow matches this combination that you were using.

According to the spec, the best way to do that is to use a hue preserving and lightness preserving algorithm. Unfortunately, that's not what browsers implemented. They are using naive clipping technique. If you use a combination that exceeds the values, they clip it and give you a color.

And that can yield very unexpected results. Here's a demo to illustrate that with a couple of rows here and the first row, I'm using the combination 90% lightness, 15% chroma, and 0 degrees. And gives us this light pink color, which is fine. And this color is still in gamut.

If I -- increased the 15% to 16%, we are out of gamut, but the color still looks the same, that's fine. If I change to 17, 18, 19, 20%, we're still fine. We're out of gamut, but it's OK. The color still looks the same. But if you increase to 20%, 40%, 60, 80,%, we get a completely different color. So if we use a color end up wide out of gamut, you may get a different color, which is bad.

Here's a demo by Miriam Suzanne, you have a comparison here on the left, you see how color is implemented in the browser today, on the right, you'll see ColorJS, a JavaScript library for managing colors. And what Miriam did here, she defined a hue of 242, chroma50, and in scale, you can see transition of 100% lightness to 0% lightness. And at the beginning, I told you 100% means white, but on the left here, you don't see white, you see a light blue, and zero means black, but you don't see black, you only see a dark blue.

This gradient -- yeah, this transition doesn't really look nice. Here we have an implementation according to spec and you can see how it starts with white and ends with black. And this is much better and nicer and much more expected.

There's a GitHub issue, as you can see, a lot of participants, a lot of posts, a lot of opinions. So if you want to have a bad day, read that. There's an ongoing discussion, I think last week, Miriam asked about the current status. And I haven't seen a reply yet, I think.

All right. So, if HSL is tennis, then LCH and LAB are curling. They're great, but they're just not the sport for me. That's because there's a bug in LAB and LCH. Who knew there could be bugs in color spaces? But yeah, it is what it is. And here we see that bug. And so, at the tip of the triangle, you can see a blue color, and if you transition from the blue color to white, we will expect that the blue gets lighter. And if you transition from the blue to the black, it gets darker.

What happens, it gets lighter and darker but also turns purple. And that's because there is an unexpected hue shift towards purple between the hue values 270 and 330 in LAB and LCH. That's a bug. And yeah, that's also not great. And this is why they came up with another color space, which is similarly awesome as LAB and LCH, but one is simple to use and also does a good job of predicting lightness and so on. And also, one that fixes this bub.

Bug. Now you can see a nice transition from blue to white without any shifts towards purple. And this is called OK LAB and OK LCH. Why? Because according to the paper, it's called the OK LAB color space because it's an OK LAB color space, it's just an OK implementation of LAB and LCH. Absolutely wonderful. One of my favorite things.

Here's a tool by Adam Argyll, it's color mixes, or the two rectangles at the top are color pickers. We have blue and white and we are mixing it in the LAB color space. And I'm changing the lightness, and you can see how it turns purple.

Which we don't want. And if we change that to OK LAB, it looks OK. So for you, it means most of your CSS, you probably want to use OK LAB or OK LCH. We have dedicated functions for that. With LCH, LAB, and also OK LCH and OK LAB functions.

And the numbers here look like arbitrary magic numbers because they kind of are, I don't know what's going on here. Like in OK LCH, the lightness component, 0%, 100%, but then for chroma, for the saturation, a value between 0 and 0.37. And then, we have the hue and for LAB, we have the same lightness and for A and B, we have values between minus 0.4 and plus 0.4.

Yeah. Absolutely random. Luckily, the even margins crew built OK LCH.com, with a really great color picker, you can pick your colors and you can also paste the hex value, for example, and they'll convert it to OK LCH or OK LAB. And if you look closely at these range sliders, you can see how there are some gaps.

And this is when you move the thumb into one of the wide areas, you picked the color out of gamut. It shows you if you're in gamut. It's a really cool tool. There's also a 3D representation for people smarter than me. So that's definitely something you want to check out.

So if we go back to our example, if you remember, we have this horrible light green button with HSL. If you do the same with LCH, we get a button or a color that harmonizes with the purple color.

And you may have noticed that I'm using spaces here in those functions. Josh -- Josh also mentioned that. There is a space separated function color notation. That we can use with traditional functions with RGB, instead of comments in RGB, you can use spaces which allows you to add an alpha channel. And with OK LCH and OK LAB, we have to use the space separated notation. No commas in those functions.

Cool. So we have dedicated functions for LAB and LCH and OK LAB and OK LCH, but what if you wanted to use another color space? Like RGB without gamut correction, for example? Gamma correction. Then you can use a color function. It takes up to five values. The first value is the color space that you want to use, for example, as RGB linear.

Then, you have three channels, RG and B represented by numbers 0 and 1 or 0% and 100% and option for alpha channel. And you can only do that with nonpolar color spaces, that doesn't work with HSL or LCH.

Important thing is just because you can use the 2020, doesn't mean you can display the color in the color space because your device and browser may not support that. If you want to be really safe, you can use a media feature, the two values, P3, which stands for wide gamut, and then really wide gamut. The names they picked are unfortunate, generally for really wide gamuts.

So, yeah. What's cool about having colors, different color spaces isn't just the fact that we can access more colors, but like I said, in color spaces, in different color spaces, colors are arranged differently, which also yields different results when you're creating gradients. And this is one of the most -- one of the coolest things with the color spaces. So let's say we have a linear gradient from left to right and from hot pink to aqua.

What we can do now is say, you know what, give me that gradient, but please, don't use the RGB default. Use OK LAB. We can compare the same gradient. You can see how OK LAB will give you different results than RGB, for example, OK LCH looks very similar to RGB, but the colors are more vibrant, the gradient is nicer, and probably looks better on my laptop screen than this screen, but it gives you very, very saturated colors. You may want to use OK LCH for gradients, but you don't necessarily have to. You can also try other color spaces and see if you yield different results that you may prefer.

Another really cool thing is, about color spaces and hue implementation, by default a gradient will take the shortest route. The first circle and the second circle, it'll always take the shortest route to create the gradient. And we can now, influence that. Take either the shorter hue or the longer hue on the color wheel so it doesn't go the shorter route but the other way around.

And then, the same combination of colors will give us completely different gradients. And having access to color spaces isn't just useful for gradients, but also for color mixing.

We can mix colors in different color spaces, we have three values with the color mix function. We define the color space we want to mix in and the first and second column. And this will give us this result. No reaction. Last year, I was -- I was at an event and the day before, I was there with my kids, so I didn't sleep at all.

It was 10:00 PM the day before, and I was like how do I visualize the mixing of colors? Of course, a bouncing squash ball. People will love that, but it's the fourth time of giving this presentation and no one appreciates my squash balls. OK.

Thank you. You can also specify the amount of colors you want to mix. You can say give me 70% hot pink and 30% aqua. You can actually just define 70% and automatically use 30% for different, for the other color. And if your combination of values doesn't add up to 100%, it'll take the remaining percentages and apply them as an alpha value. That's why the squash ball looks so washed out. And if you mix colors in different color spaces, it can be a very, very different result.

In all of these examples, I'm mixing hot pink and aqua with the same amount, 50%. And that was so confusing for me when I got started. I was like, OK, let's see what color mixing does for me. And I got so completely different results. This looks almost gray and we have purple and blue and something that goes, so it's green even maybe. And then, I realized the best way to deal with that is just to accept it.

It's just what it is. No, it's a feature. It's a really great thing. You want to mix colors, try different color spaces. It's something that you can use. It's a tool, it's a feature, it's great. It's perfect.

Again, Adam Argyll's tool where you can do testing, take one color and another color and then you mix it in different color spaces and you can also change to lightness. All right. So what if you can't pick color spaces? What if we are working with, I don't know, a third party that provides us with boring old hex codes?

It's another incredibly awesome addition to CSS. Probably on the same level with the hex class for me. And here's what you can do. You can say, you know what, give me that hex code, but please, represent it in LCH. We're taking from the color that we have, the color that we're provided with, we're taking the L, C, and H channels and using it in the OK LCH function.

And what's really awesome about that, of course, you can put the color into a custom property, we can actually manipulate the channels. We can say, you know what, take the lightness, the saturation, but use a different hue. And of course, we can use the correct function. We can say take the lightness but subtract 20%, for example. And you can also do something like that. So you don't necessarily have to use the channel in its respective place.

You can combine them, I'm not going to pretend that I understand what's going on here. I copied it from the spec. So if you go back to my very first example, the notification component, if we wanted to write that in with the relative color syntax, we have to think differently. We have to challenge the way we approach CSS a little bit. And that's what I like a lot about many of the new things we have in CSS now.

It challenges us to completely change the way we approach CSS compared to how we used to. So, here's how I did it. Maybe there are better ways. First, I define a base color, this is the blue color for this default state of my component. And then, I say based on that color, give me two variations. So I'm reusing the lightness and the chroma and only changing the hue because I want the yellow and red variation.

Then, I have this private custom property, which isn't a thing, it's made up. But I'm using an underscore tool to tell me and my colleagues this is custom property inside the component, it's not supposed to be exposed to the outside. And if someone provides a color, take that color.

If there's no color provided, just fall back to the info color. And we use a color for the background, and for the border, we change the lightness and the chroma little bit and take the hue and that's it. And now, we can do the same thing in a very, very flexible way, and very dynamic and powerful way for color systems.

Cool. And -- exciting addition to CSS, color fonts. I didn't know those existed. Here is random arrangement of randomly picked letters. And if you look at that example, at first, it doesn't look too special. But you may realize, hey, there are multiple colors here. There isn't just one color for this text, but actually four colors. And yeah, that's what color fonts are about. They can display colors by default, and multiple colors, which is really cool and they also come with different palates, sets of colors.

For example, this font here. This font here comes with 11 palates. And we have access to them in CSS. Make your photos. That's a good one to share.

The last one I put in for you -- so, there's add font palate where you define the font palates, you have to define a name using the dash dash syntax. There's a proper name for that. But I can't remember it now, and I have to ask later. There's the font family that you have to define, and then, you say which palate you want. Starts zero, so it's zero based, if you want the default one, define zero and define a number.

And then, at the very bottom online 17, you can see how I reference the palates, the custom value -- it's a custom indent. The dash dash syntax. Browser people arguing.

So, again, font palate property and then you reference the palate you created. How do I know which palates the font gives me? Well, you can use the website, which is such a great name and such a great design. It's about fonts but doesn't look like it's about fonts. Drop a font.

It will tell you all kind of things about your font, like how many glyphs and the size and also the different palates and also the axis, by the way if it's barrier fonts.

You can also overwrite colors. You can say, give me the ninth or tenth palates, but I don't like the last color. So, overwrite the color property, which expect a list of two values, the first value is the color you want to change starting at zero, again, and the second value is the color.

And, of course, if you wanted to create a custom palate, you replace all of the colors and this will give you another one. Another combination. Then, there is color schemes. Which is another exciting thing. We already heard about that today. So I just removed all of the slides. You know everything you need to know by Niya, thank you.

Less talking for me. One thing I can point you to is this article by Joy, who will speak today, as well. Come to the light-dark() side, and goes into detail about this topic.

And that's it for me. OK. I probably have to explain that. As you can hear, I'm not a native speaker, I went on all kind of stock photo sites and searched for squash. And I got some squash, like the game, and then, I got this, and I was like -- what's going on? Yeah, then I pulled up the dictionary and was like, OK, that's also squash. Cool.

And if you're wondering why squash? Why I picked the topic squash and showed you video, there's absolutely no reason. It's completely random. One night, I woke up and was like, you know what would be really cool if I just would play that video at the conference in front of, I don't know, 500 people. And then, a couple of months later, Mark wrote me and said, hey, do you want to speak? Yeah, I already have something --

Yeah. So that's it, of course, there's more, actually, I had some more stuff in my slides, but I only have 40 minutes today. What I'm asking you to do now is to take that stuff and do some experimenting. Because color is really -- color is really exciting, but you don't see people write too much about it. So, take some of the stuff you learn today, maybe, and try it out, create some demos, write some blog posts.

I want to see what you can come up with, and yeah. That's it, thank you so much.

Transcript by Diane (in Iowa) from White Coat Captioning

About Manuel Matuzović

Manuel Matuzović

Manuel is a freelance frontend developer, accessibility auditor, teacher, author, and consultant who’s passionate about the web. He writes about accessibility, HTML, and CSS on his personal blog matuzo.at and on htmhell.dev.