• Thu. Dec 9th, 2021

Dynamic Color Manipulation with CSS Relative Colors

Byadmin

Nov 25, 2021


I was reading Dave’s post “Alpha Painlet” when I first learned about CSS relative colors.

🤯

CSS relative colors enable the dynamic color manipulation I’ve always wanted in vanilla CSS since Sass’ color functions first came on the scene (darken(), lighten(), etc.).

Allow me to explain a bit more about why I’m so excited.

Dynamic Colors in CSS via Transparency

I’ve written about generating shades of color using CSS variables, which details how you can create dynamic colors using custom properties and the alpha channel of a supporting color function. For example:

:root {
  --color: 255 0 0;
}

.selector {
  background-color: rgb(var(--color) / 0.5);
}

However, there are limitations to this approach.

First, all your custom property color values must be defined in a color space whose notation supports the alpha channel in its color function, like rgb(), rgba(), hsl(), and hsla(). For example:

:root {
  --color-rgb: 251 0 0;
  --color-hsl: 5 10% 50%;
}

.selector {
  background-color: rgb(var(--color-rgb) / 0.5);
  background-color: hsl(var(--color-hsl) / 0.5);
}

You can’t “coerce” a custom property’s color value from one type to another:

:root {
  --color: #fb0000;
}

.selector {
  
  background-color: rgb(var(--color) / 0.5);
}

Dynamic colors in CSS using HEX color values is impossible. While you can specify the alpha channel for a HEX color, you can only do so declaratively (i.e. #ff000080). CSS has no notion of concatenating strings.

:root {
  --color: #ff0000;
}

.selector {
  
  background-color: var(--color) + "80";
}

And if you’re using named colors in CSS, well, you’re flat out of luck trying to do anything dynamic.

:root {
  --color: green;
}

.selector {
  
  background-color: var(--color) + "opacity: .5";
}

However, with relative colors in CSS this all changes!

You can declare a custom property’s value using any color type you want (hex, rgb, hsl, lch, even a keyword like green) and convert it on the fly to any other color type you want.

:root {
  --color: #fb0000;
}

.selector {
  
  background-color: rgb(var(--color) / 0.5);
  
  
  background-color: rgb(from var(--color) r g b / .5);
}

It even works with color keywords!

:root {
  --color: red;
}

.selector {  
  background-color: rgb(from var(--color) r g b / .5);
}

The easiest way for me to describe what’s happening here is to borrow terminology from JavaScript. With relative colors in CSS, you can declaratively perform a kind of type coercion from one color type to another and then destructure the values you want.

I don’t know if that blows your mind as much as it blew mine, but take a minute to let that soak in. Imagine the possibilities that begin to open up with this syntax.

Dynamic Colors in CSS via calc()

Dynamically changing colors using the alpha channel has its drawbacks. Transparent colors blend into the colors upon which they sit (you’re not always blending into white). You can take a color and get a “slightly lighter” version by changing its opacity, but that color won’t be the same everywhere. It is dependent upon which color(s) it sits on top of.

Sometimes you need a “slightly lighter” color without transparency. One that is opaque.

Or sometimes you need a “slightly darker” color, in which case you can’t set the alpha channel to 1.2 hoping it’ll get slightly darker.

Previously, you could achieve this flexibility in CSS by becoming incredibly verbose in your custom property definitions and defining each channel individually.

:root {
  
  --color-h: 0;
  --color-s: 100%;
  --color-l: 50%;
}

.selector {
  
  color: hsl(
    var(--color-h),
    calc(var(--color-s) - 10%),
    var(--color-l)
  );
}

This could get really verbose really fast. And color values like hexadecimal colors are not supported.

With CSS relative colors, this is now dead simple in combination with calc().

:root {
  --color: #ff0000;
}
.selector {  
  color: hsl(from var(--color) h calc(s - 10%) l);
}

Wild! A few more examples, for completeness:

:root {
  --color: #ff0000;
}

.selector {
  
  
  
  color: hsl(from var(--color) h s l / .5);
  
  
  color: hsl(from var(--color) calc(h + 180deg) s l);
  
  
  color: hsl(from var(--color) h calc(s + 5%) l);
  
  
  color: hsl(
    from var(--color)
    calc(h + 10deg)
    calc(s + 5%)
    calc(l - 10%)
    /
    calc(alpha - 15%)
  );
}

Amazing! Sass color functions, let me show you the door.

Conclusion

Desctructuring? Type coercion? Do those words belong in a post about CSS? Is CSS a programming language?

The only thing we need now is the ability to have user defined custom functions in CSS—then you could create your own reusable lighten() and darken() functions.

But I digress.

Support for this syntax shipped in Safari Technology Preview 122 (check out some of the tests to see examples of the syntax). At the time of this writing, it’s still an experimental feature so you have to enable it via the menubar “Develop > Experimental Features”.

Related resources:



source

Leave a Reply

Your email address will not be published. Required fields are marked *