Custom Properties vs. CSS Variables: When Registration Matters
People often use “CSS variable” and “custom property” interchangeably, but they are not the same. A CSS variable is simply a placeholder for a value you want to reuse. A custom property, defined with the @property at-rule, is a variable that carries its own specification: its syntax, initial value, and inheritance behavior. That distinction opens up animation possibilities that plain variables cannot deliver.
The classic demonstration is gradient interpolation. A registered custom property can be told to hold, say, a color or a percentage value. Once the browser knows the syntax, it can transition between two values smoothly. Without registration, a variable is treated as a string, and strings cannot be interpolated.
@property --circleSize {
syntax: "<percentage>";
inherits: false;
initial-value: 10%;
}
div { /* red div */
clip-path: circle(var(--circleSize) at center bottom);
transition: --circleSize 300ms linear;
}
section:hover div {
--circleSize: 125%;
}
For instance, you can register --circleSize as a percentage with a default of 10% and disable inheritance. Then, using clip-path: circle() and transitioning that registered property, you can produce something close to Material Design’s ripple effect with pure CSS.
Chaining Multiple Registered Properties
That same idea scales well beyond a single property. Consider a component that animates text by sliding it vertically while changing the colors of a repeating gradient behind it. The gradient needs four separate values to change: two colors, a length, and an angle. Each one must be registered as a custom property with its proper syntax:
@property --c1 {
syntax: "<color>";
inherits: false;
initial-value: rgb(224, 236, 236);
}
@property --c2 {
syntax: "<color>";
inherits: false;
initial-value: rgb(92, 198, 162);
}
@property --l {
syntax: "<length> | <percentage>";
inherits: false;
initial-value: 5px;
}
@property --angle {
syntax: "<angle>";
inherits: false;
initial-value: 180deg;
}
.text {
background: repeating-linear-gradient(
var(--angle),
var(--c1),
var(--c1) 5px,
var(--c2) var(--l),
var(--c2) 6px);
}
This registration allows each value to be transitioned independently. At the same time, the text container itself can be moved up two lines worth of height (-2lh) on hover, revealing a new character in a fixed 1lh viewport. The component clips the overflow so only one character shows at a time.
.text {
background: repeating-linear-gradient(
180deg,
rgb(224, 236, 236),
rgb(224, 236, 236) 5px,
rgb(92, 198, 162) 5px,
rgb(92, 198, 162) 6px);
background-clip: text;
color: transparent; /* to show the background underneath */
background-size: 20% 20%;
}
The text is made transparent, and a repeating-linear-gradient() is applied to the background. With background-clip: text, the gradient renders inside the glyphs, creating a striped appearance.
Updating Values On Interaction
When the container is hovered or focused, you redeclare the registered properties with new values. In this particular setup:
--c1starts atrgb(224, 236, 236)and goes topink.--c2starts atrgb(92, 198, 162)and becomestransparent.--lgoes from5pxto100%.--arotates from180degto90deg.
The effect is a coordinated change: the gradient’s colors shift, its stripe spacing expands, and the whole pattern rotates. Meanwhile the text glide upward one character at a time.
Explicit Transitions
One detail worth observing is how the transition is defined on the text element. Instead of using the all keyword, which would make the browser watch every possible property, each transitioning property is listed explicitly. That is a deliberate choice to keep the browser from monitoring more than it needs to and to keep the animation cheap.
.text {
transition: --l, --angle, --c1, --c2, background-size, transform 2.4s ease-in-out;
transition-duration: 2s;
}
The Takeaway
Plain CSS variables are great for maintainable code and a few clever gimmicks. But when an animation requires swapping out one value inside a property that accepts multiple values, like the colors in a gradient, you need the specification that @property provides. Defining the syntax, initial value, and inheritance rules for each variable turns static placeholders into interpolatable values. That turns what once demanded JavaScript into nimble, declarative CSS animation.



