Safari Technology Preview added support to three new CSS color features that are coming soon to other browsers. To test the features mentioned in this article you need to enable them on the Develop menu.
Let's review them and see how they apply to creating a color theme.
CSS Relative color syntax
Relative color allows us to manipulate and convert any color to any format. We can use it to create a color palette from any chosen color.
:root {
--theme-primary: #8832CC;
}
We want to create a color palette based on our primary color. Each step should have the same hue and saturation as the original, but a different lightness value, going from lighter to darker.
We'll convert our color to the HSL format (hue, saturation, lightness) and modify the lightness parameter using the new Relative color syntax with the from
keyword.
Not only we converted our primary color from Hex to HSL, we also made it darker by changing its lightness value to 30%.
Using this, we can now create our palette:
.bg-primary-100 {
background-color: hsl(from var(--theme-primary) h s 90%);
}
.bg-primary-200 {
background-color: hsl(from var(--theme-primary) h s 80%);
}
.bg-primary-300 {
background-color: hsl(from var(--theme-primary) h s 70%);
}
...
Because we're using CSS variables, if our --theme-primary
color changes, all our .bg-primary-xxx
classes will update automatically.
CSS Color-contrast
Our color palette and theme are looking great, and this might be just what we need. But sometimes that's not exactly what you want.
Say we want to allow the user to choose their background color, and have the rest of the colors to adapt to that background.
Here we're not looking to modify the original color, we need to modify our text and other elements to be light or dark depending on the chosen primary color. This is where the new color-contrast
css function comes in handy.
.text-contrast-primary {
color: color-contrast(var(--theme-primary) vs white, black);
}
Our .text-contrast-primary
class will automatically compare our --theme-primary
color to the specified options white
or black
and choose the one with the best contrast.
Similarly we can add a class for a contrast background. To take things further let's also support Tailwind's bg-opacity
classes so we can fade our background color.
.bg-contrast-primary {
/* get which color we should use (white or black) */
--contrast-color: color-contrast(var(--theme-primary) vs white, black);
--tw-bg-opacity: 1;
/* converting white or black to rgba to support alpha */
background-color: rgba(from var(--contrast-color) r g b var(tw-bg-opacity));
}
Our content section is done. The sidebar and navigation have the proper background color, but we can't use .text-contrast-primary
on them since that's the same color as the background. We need the inverse of our contrast color.
So let's add this awkwardly long named class to fix that:
.text-contrast-primary-inverted {
/* get contrast color */
--contrast-color: color-contrast(var(--theme-primary) vs white, black);
/* get the contrast color of that contrast color */
color: color-contrast(var(--contrast-color) vs white, black);
}
And here's our html:
<body class="bg-primary">
<nav class="bg-contrast-primary bg-opacity-70 text-contrast-primary-inverted">Navigation</nav>
<main class="text-contrast-primary">Content</main>
<aside class="bg-contrast-primary bg-opacity-70 text-contrast-primary-inverted">Sidebar</aside>
</body>
CSS color-mix
In our first example we made a color palette based on the Hue and Saturation of our primary color. If we apply that same code to this other color we get this:
Our original color doesn't appear in our palette. It falls somewhere between the second and third shade, not close to the middle. If we want our palette to be lighter and darker shades of our primary color we can use color-mix
instead.
.text-primary-lightest {
color: color-mix(var(--theme-primary), white, 30%);
}
.text-primary-dark {
color: color-mix(var(--theme-primary), black 10%);
}
.text-primary-darker {
color: color-mix(var(--theme-primary), black 20%);
}
...
You can mix any two colors this way, and specify different quantities, just like mixing real paint.
Now we have a color for error messages that matches better with our primary color.
.text-error {
color: color-mix(var(--theme-primary), #FF0000 50%)
}
I hope you enjoyed this article. Comment which feature is your favorite and what other ways you can see yourself using them.
Top comments (7)
Thanks again for this article!
So much potential...
"Because we're using CSS variables, if our --theme-primary color changes, all our .bg-primary-xxx classes will update automatically."
Can this technique used to "automatically update"
.bg-primary-xxx
classes to minimum accessibility foreground/background contrast ratios (4.5:1)? Rather than a fixed black or white?Seeing the comment just now.
You can add any colors instead of white and black, you can even add more colors. The browser will pick the first one with good contrast. The spec for color-contrast is still in draft so things might change.
But for example you can do:
When changing primary color the background will be the primary color but text color will be a darker or lighter version of that same color automatically.
Awesome.
Great Article, thanks!
Fantastic article! Great guide for color ignorant people like myself. Thank you!
I tried to use it with scss like
background-color: hsl(from var(--btn-color) h s 70%);
but I got an error sayes:
Error: wrong number of arguments (1 for 3) for `hsl'
This is really great. Thank you for sharing!