CSS @property: Typed Custom Properties
Register CSS custom properties with a type, initial value, and inheritance behavior to enable animation and type safety.
Overview
Key Points
- •@property must be at the top level, not inside a selector
- •syntax, inherits, and initial-value are all required (unless syntax is *)
- •Typed custom properties can be animated and transitioned natively
- •The browser validates values against the declared syntax at assignment time
- •inherits: false creates a fresh value for every element that does not explicitly set the property
- •Use @property for gradient angles, hue rotations, counter values, and progress indicators
- •Fallback: unregistered custom properties still work, they just cannot be animated
- •Supported in Chrome 85+, Safari 16.4+, Firefox 128+
Examples
Animating a gradient angle
/* Without @property this would not animate smoothly */
@property --angle {
syntax: '<angle>';
initial-value: 0deg;
inherits: false;
}
.spinning-gradient {
background: conic-gradient(from var(--angle), #6366f1, #8b5cf6, #ec4899, #6366f1);
animation: spin-hue 4s linear infinite;
}
@keyframes spin-hue {
to { --angle: 360deg; }
}Without @property, CSS cannot interpolate between "0deg" and "360deg" because it treats the custom property as a string. With @property, it knows --angle is an angle and can animate it.
Animating custom property for progress
@property --fill {
syntax: '<number>';
initial-value: 0;
inherits: false;
}
.progress-bar {
background: linear-gradient(
to right,
#6366f1 calc(var(--fill) * 100%),
#e5e7eb calc(var(--fill) * 100%)
);
height: 8px;
border-radius: 4px;
transition: --fill 0.6s ease;
}
.progress-bar.loaded {
--fill: 0.75; /* 75% */
}Because --fill is typed as a number, the browser can interpolate between 0 and 0.75, animating the gradient fill.
Type-safe color token with @property
@property --theme-primary {
syntax: '<color>';
initial-value: oklch(0.55 0.2 264);
inherits: true;
}
/* Change the theme by setting the property */
.theme-emerald {
--theme-primary: oklch(0.55 0.18 155);
}
/* All children inherit the updated color */
.btn {
background: var(--theme-primary);
transition: --theme-primary 0.4s ease; /* can animate between colors */
}With syntax: color, reassigning --theme-primary and transitioning it creates a smooth color shift across the entire component tree.
Hue rotation animation
@property --hue {
syntax: '<number>';
initial-value: 0;
inherits: false;
}
.hue-cycle {
filter: hue-rotate(calc(var(--hue) * 1deg));
animation: cycle-hue 6s linear infinite;
}
@keyframes cycle-hue {
to { --hue: 360; }
}
/* Also works for oklch-based themes */
.dynamic-bg {
background: oklch(0.55 0.2 calc(var(--hue) * 1deg));
animation: cycle-hue 8s linear infinite;
}Common Mistakes
- Putting @property inside a selector (it must be at the top level of the stylesheet)
- Omitting initial-value when inherits: false is set (required by spec)
- Expecting @property to work as a JavaScript-accessible Typed OM variable (it is CSS-only)
- Using --property: value inside @property (the descriptor names are syntax, inherits, initial-value without -- prefix)
Note
Use @property whenever you want to animate a custom property. If the animation looks like a discrete jump instead of a smooth interpolation, the property likely lacks a @property registration.
Exercises
Write CSS to style the HTML scaffold. The preview updates live — when it looks right, mark as done.
Completed: 0 / 2 exercises
Knowledge Check
Question 1 of 3