Making Custom Properties (CSS Variables) More Dynamic
This article originally appeared on CSS Tricks on May 10, 2017.
Most of the power comes from two unique abilities of Custom Properties:
- The cascade
Even more power is exposed as you combine Custom Properties with other preexisting CSS concepts, like
You can use Custom Properties to do effectively what variables in preprocessors like Sass provide - set a global or scoped variable value, and then use it later in your code. But thanks to the cascade, you can give new property values inside a more specific rule.
People have been discussing these benefits from the cascade for a few years now, but it often gets lost in the conversation despite being a key functionality that differentiates it from preprocessors. Amelia Bellamy-Royds discussed it in the context of SVG and
use in 2014, Philip Walton noted a lot of these general cascading benefits in 2015, and last year Gregor Adams showed how they can be used in a minimal grid framework. Taking advantage of the cascade is likely the easiest way to start working with Custom Properties with progressive enhancement in mind.
Okay. Now that we know Custom Properties can natively give us some functionality preprocessors have and some new uses thanks to the cascade - do they give us anything we simply never could do before?
All of the properties that have multiple parts are able to be used differently now. Multiple
backgrounds can be separated, and multiple
transition-durations can be broken out individually. Instead of taking a rule like
transform: translateX(10vmin) rotate(90deg) scale(.8) translateY(5vmin) you can set one rule with several custom properties and change the values independently thereafter.
It takes a bit to initialize, but then that little bit of extra effort up front sets you up to modify each transform function independently based on the needs of the class/selector rule. Your markup can then include any or all of the classes defined on each
.view element and the
transform will update appropriately, as the demo based on this code sample shows.
While independent transform properties are coming (and at that time
rotate will be first level citizens), they are currently only in Chrome behind a flag. With Custom Properties you can get this functionality today with more support (and the additional ability to define your own order of functions, since
rotate(90deg) translateX(10vmin) is different than
translateX(10vmin) rotate(90deg), for example).
If you are okay with them sharing the same timing options, they can even animate smoothly when using
transition when changing any of the variables. It’s kind of magical.
See the Pen CSS Variables + Transform = Individual Properties (with Inputs) by Dan Wilson (@danwilson) on CodePen.
Going from No Units to All the Units
You can build on these concepts when combining with
calc(). Instead of always setting variables as above with units (
--card-width: 10vmin or
--rotation-amount: 1turn) you can drop the units and use them in more places with a relation to one another. Now the values in our Custom Properties can be more dynamic than they already have been.
calc() has been around for a few years now, it has arguably been most useful when trying to get a result from adding values with different units. For example, you have a fluid
width in percentage units that needs to be shortened by 50px (
width: calc(100% - 50px) ). However,
calc() is capable of more.
Other operations like multiplication are allowed inside
calc to adjust a value. The following is valid and gives us a sense that the transforms and filters are related to one another since they all use the number 10.
This likely isn’t as common a use case because it is a calculation you don’t need the browser to compute.
10 * 1vw will always be
10vw so the calc gives us nothing. It can be useful when using a preprocessor with loops, but that is a smaller use case and can typically be done without needing CSS
But what if we replace that repeated
10 with a variable? You can base values from a single value in multiple places, even with different units as well as open it up to change values in the future. The following is valid thanks to unitless variables and
The single value can be taken (initially 10, or later changed to 80… or any other number) and applied separately to
vw units or
vh units for a translation. You can convert it to
deg for a rotation or a
You don’t have to drop the units on the variable, but as long as you have them in your
calc you can, and it opens up the option to use it in more ways elsewhere. Animation choreography to offset durations and delays can be accomplished by modifying the base value in different rules. In this example we always want
ms as our end unit, but the key result we want is for our
delay to always be half the animation’s
duration . We then can do this by modifying only our
Even cubic beziers are up for Custom Properties modification. In the following example, there are several stacked boxes. Each one has a slightly smaller scale, and each is given a cubic bezier multiplier. This multiplier will be applied individually to the four parts of a baseline cubic-bezier. This allows each box to have a cubic bezier that is different but in relation to one another. Try removing or adding boxes to see how they play with one another. Press anywhere to translate the boxes to that point.
If you are viewing this in certain browsers (or are wondering why this example has an
.advanced-calc class) you might already suspect there is an issue with this approach. There is indeed an important caveat…
calc magic does not always work as expected across the browsers. Ana Tudor has long discussed the differences in browser support for
calc , and I have an additional test for some other simplified
calc use cases.
The good news: All the browsers that support Custom Properties also largely work with
calc when converting to units like
rem, and other linear distance units inside properties such as
The not-so-good news: Firefox and Edge often have problems with other unit types, such as
ms , and even
% in some contexts. So the previous
filter: hue-rotate() and
--rotation properties would be ignored. They even have problems understanding
calc(1 * 1) in certain cases so even remaining unitless (such as inside
rgb()) can be a problem.
While all the browsers that support Custom Properties will allow variables inside our
cubic-bezier , not all of them allow
calc at any level. I feel these
calc issues are the main limiting factors with Custom Properties today… and they’re not even a part of Custom Properties.
There are bugs tracked in the browsers for these issues, and you can work around them with progressive enhancement. The earlier demos only do the
cubic-bezier modifications if it knows it can handle them, otherwise you get the baseline values. They will erroneously pass a CSS
@supports check, so a JS Modernizr-style check is needed:
This will set a new value for a globally defined property (in CSS defined in the
:root rule). Or you can go more direct and set a new value for a specific element (and thus give it the highest specificity for that element, and leave the variable unchanged for other elements that use it). This is useful when you are managing state and need to modify a style based on given values.
David Khourshid has discussed powerful ways to interact with Custom Properties via JS in the context of Observables and they really fit together nicely. Whether you want to use Observables, React state changes, tried-and-true event listeners, or some other way to derive value changes, a wide door is now open to communicate between the two.
This communication is especially important for the CSS properties that take multiple values. We’ve long had the
background rule, we have to know which one we are modifying and then make sure we leave the other nine alone. This gets even more complicated for
transform rules when you are trying to only modify a
rotate() and keep the current
The unitless approach works well here, too. Your
Is it Time to Use This?
As Custom Properties are now in the latest browsers from Mozilla, Google, Opera, Apple, and Microsoft - it’s definitely a good time to explore and experiment. A lot of what is discussed here can be used now with sensible fallbacks in place. The
calc updates needed in some of the browsers are further out, but there are still times when you can reasonably use them. For example, if you work on hybrid mobile apps that are limited to more recent iOS, Android, or Windows versions you will have more room to play.
Custom Properties present a big addition to CSS, and it can take some time to wrap your head around how it all works. Dip your toes in, and then dive in if it suits you.