We can make variables in CSS pretty easily:
:root {
--scale: 1;
}
And we can declare them on any element:
.thing {
transform: scale(var(--scale));
}
Even better for an example like this is applying the variable on a user interaction, say :hover:
:root {
--scale: 1;
}
.thing {
height: 100px;
transform: scale(var(--scale));
width: 100px;
}
.thing:hover {
--scale: 3;
}
But if we wanted to use that variable in an animation… nada.
:root {
--scale: 1;
}
@keyframes scale {
from { --scale: 0; }
to { --scale: 3; }
}
/* Nope! */
.thing {
animation: scale .25s ease-in;
height: 100px;
width: 100px;
}
That’s because the variable is recognized as a string and what we need is a number that can be interpolated between two numeric values. That’s where we can call on @property to not only register the variable as a custom property, but define its syntax as a number:
@property --scale {
syntax: "<number>";
initial-value: 1;
inherits: true;
}
Now we get the animation!
You’re going to want to check browser support since @property has only landed in Chrome (starting in version 85) as of this writing. And if you’re hoping to sniff it out with @supports, we’re currently out of luck because it doesn’t accept at-rules as values… yet. That will change once at-rule()becomes a real thing.
Using
@supports not (inherits: true)will do nothing because inherits is not a valid CSS property in any browsers so the notice will show even if the browser support@propertyinherits is a descriptor that is only valid inside the
@property.To test the support we need to use
at-rule()but its a new feature not yet implemented (https://www.bram.us/2022/01/20/detect-at-rule-support-with-the-at-rule-function/)Huh! I thought I had tested that but clearly I was wrong!
Fascinating!
What’s the benefit of using this instead of just using numbers? If someone could explain I’d really appreciate it!
I think that’s the point: custom properties can be updated and interpolated, but we have to first define them as a number; otherwise they’re strings instead of being treated as integers.
--my-var: 7;Doing JS
getComputedStyle().getProperty('--myvar')returns" 7"– a string with a leading whitespace. You can’t do calculations with strings.You can animate components of a property’s value independently, with different animation durations, delays, timing functions and so on.
For example, consider a simple
transformchain with a rotation and then a translation – this is the basic technique for positioning items on a circle.Uniformly rotating an item on a circle of radius
5emis achieved with:But let’s say we also want to vary the radius of this circle independently of the rotation. Well… before being able to animate custom properties, we couldn’t do it without an extra child or pseudo. That’s because CSS animates entire property values with CSS animations, not parts of them, so we’d have to break the
transformvalue to have the rotation on our item and the translation on a child or pseudo.Enter custom properties! They’re properties, so their values can be animated if we register them.
I detailed this with a lot more examples in this article.
In a similar manner, we can animate the angle and the stop position of a
linear-gradient()differently:Note that gradients cannot be animated at all in any other way in any of the current browsers (though IE10+ and pre-Chromium Edge could animate them).
Or components of a
clip-pathvalue:Or components of a
filtervalue:And regarding the JS part: they make it a lot easier to modify just parts of a value via JS. For example, let’s say we have a cursor-following spotlight created with a
radial-gradient():When the cursor moves, we update just the
--xand--yvalues via JS:… instead of writing an entire sausage like:
Honestly, it just seems a bit ridiculous that CSS has not been officially converted to a scripting language by now.
I mean, it’s selector syntax and engine has supplanted DOM methods! So it already handled arrays (of CSS class based nodes).
It clearly has conditional tests with @ rules that even have faux ‘else if’ and ‘else’ capability by wrapping sets of rules into queries for different device parameters and then allowing, I guess, an ‘also’ rather than an ‘else’ with every other rule running as well.
Variables … that are now getting strict type definition support?
That’s more than JS itself has, at least by default, isn’t it? Hence TypeScript’s existence.
Meanwhile we’ve got SASS when might it not be better to boost CSS up to SASS-like capabilities so that it remains under the W3C umbrella?
Hmmmm.
Yes, I know what you’re all thinking: clear separation of style, markup and behaviour sounds sensible. But does it even really exist at present? Maybe much less than we’d like to think.
We can test and adapt code for
@propertysupport without@supports.This is what I do to style things differently:
First, register a
--houdinicustom property and set its initial value to1.Then set a support flag as a custom property
--sto the--houdinivariable with a fallback of0. If registering custom properties isn’t supported,--sis going to have the--houdinifallback value (0). If we have support for registering custom properties,--swill take the initial value we have set for Houdini (1). So--sis basically a flag that’s0for the no support case and1otherwise. This flag can be used to set different numeric values depending on which case we’re in.Live test.
We can also make some elements be displayed only in the
@propertysupport case:We can also use the negation of
--sin other cases:I’ve detailed such techniques in the DRY Switching with CSS Variables articles.