@property — Typed Custom Properties
--angle: 45deg as a raw token stream, not as an actual angle. That works fine for simple substitution, but it silently breaks two things developers reach for constantly: smooth animation of the value, and predictable fallback behavior for invalid input. The @property at-rule registers a custom property with an explicit type, a default value, and an inheritance setting, turning a plain variable into something the CSS engine actually understands.Registering a typed custom property
@property --gradient-angle {
syntax: '<angle>'; /* the type — an angle, not just any text */
inherits: false; /* does NOT inherit down to children */
initial-value: 0deg; /* used before it's ever set, and as a fallback */
}syntax — the value type, using the same syntax component names as CSS itself:
<color>,<length>,<percentage>,<number>,<angle>,<length-percentage>, or a custom set of allowed keywords likered | green | blue.inherits —
trueorfalse, controlling whether child elements pick up the value the way normal custom properties do.initial-value — the value used before anything sets the property, and (critically) the value used when an invalid value is assigned.
Capability #1: the browser can now interpolate the value
@property tells the browser exactly what kind of value it is, so it can interpolate it the same way it interpolates a built-in property like opacity or transform./* WITHOUT @property — this custom property is just text.
Animating it does nothing useful: the browser can't find a
midpoint between the string "0deg" and the string "360deg",
so the value jumps instantly instead of animating smoothly. */
.plain {
--angle: 0deg;
background: conic-gradient(from var(--angle), #0066cc, #ff6600, #0066cc);
transition: --angle 1s linear; /* silently does nothing */
}
.plain:hover {
--angle: 360deg;
}/* WITH @property — the browser now knows --gradient-angle
is an <angle>, so it can compute every intermediate value
during the transition, producing a genuinely smooth spin. */
@property --gradient-angle {
syntax: '<angle>';
inherits: false;
initial-value: 0deg;
}
.spinner {
--gradient-angle: 0deg;
background: conic-gradient(
from var(--gradient-angle),
#0066cc,
#ff6600,
#0066cc
);
transition: --gradient-angle 1s linear;
}
.spinner:hover {
--gradient-angle: 360deg; /* now animates through every angle in between */
}@property, the exact same effect is one transition or @keyframes rule away.Capability #2: invalid values fall back to the default
syntax at assignment time, and substitutes the registered initial-value if the value doesn't match.@property --card-radius {
syntax: '<length>';
inherits: false;
initial-value: 8px;
}
.card {
border-radius: var(--card-radius);
}
.card.bad {
/* Not a valid <length> — falls back to 8px instead of
breaking or being ignored downstream */
--card-radius: not-a-length;
}Registering from JavaScript
@property is actually built on:CSS.registerProperty({
name: '--gradient-angle',
syntax: '<angle>',
inherits: false,
initialValue: '0deg',
})Aspect | Plain --custom-property | @property-registered |
|---|---|---|
Type checking | None — any token stream is accepted | Validated against |
Animatable / transitionable | No — jumps instantly | Yes — interpolates smoothly |
Invalid value behavior | Kept as-is; can invalidate the rule using it | Falls back to |
Inheritance | Always inherits | Explicit |
@property rule missing syntax, inherits, or initial-value is treated as invalid and ignored entirely, so the plain untyped behavior applies instead. All three are required together.