CSS @property is an at-rule that registers a custom property with a type, initial value, and inheritance behaviour — transforming it from an opaque string substitution (which is what unregistered --custom-property variables are) into a typed CSS value that the browser understands as a specific <color>, <length>, <number>, <percentage>, <angle>, <time>, or other CSS type. The most impactful consequence of registering a custom property: the browser can interpolate it in @keyframes and CSS transitions, enabling animations that are impossible with unregistered variables. An unregistered variable like --hue: 0 animated to --hue: 360 inside @keyframes is treated as two discrete string values — the browser switches from one to the other at the 50% mark with no interpolation. A registered @property --hue { syntax: "<number>"; inherits: false; initial-value: 0; } is animated as a number — the browser interpolates all 360 values smoothly between 0 and 360. The syntax descriptor defines the type using CSS value definition syntax: <color>, <length>, <number>, <percentage>, <integer>, <angle>, <resolution>, <transform-list>, <custom-ident>, or * (any, same as unregistered); the pipe | character combines types (<number> | <percentage>). The inherits descriptor controls whether the property’s value cascades from parent to child elements — false means each element gets its own initial value, which is essential for per-element animated properties used on multiple elements simultaneously. initial-value is required when inherits: false — it defines the starting value for elements that do not set the property explicitly. Browser support: Chrome 85+, Edge 85+, Safari 16.4+, Firefox 128+ — as of 2024 all evergreen browsers support @property, making it safe for WordPress themes targeting modern browsers. In WordPress themes, @property registered in style.css or a stylesheet enqueued via wp_enqueue_style() can create animatable gradient angles, hue-rotation effects, and counter animations that previously required JavaScript. The scroll-driven animations post used animation-timeline for scroll-triggered effects; @property provides the typed variable layer that makes those animations interpolate custom values smoothly.
Problem: A WordPress block theme uses a gradient hero banner whose angle is set as a CSS custom property (--hero-angle: 135deg) to allow easy customisation. Adding a CSS animation to rotate the gradient angle from 0deg to 360deg produces only two states (0% and 100% keyframes snap) because the browser treats --hero-angle as a string, not an angle.
Solution: Register --hero-angle as a typed @property with syntax: "<angle>", then use a standard @keyframes animation — the browser now interpolates the angle smoothly across all 360 degrees without any JavaScript.
/* ── Register typed custom properties ────────────────────────────────────── */
@property --hero-angle {
syntax: "";
inherits: false;
initial-value: 135deg;
}
@property --hero-hue {
syntax: "";
inherits: false;
initial-value: 240;
}
@property --card-progress {
syntax: "";
inherits: false;
initial-value: 0%;
}
/* ── Animated gradient hero using @property ──────────────────────────────── */
.wp-block-cover.is-style-animated-gradient {
/* @keyframes can now interpolate --hero-angle as a real angle */
animation: rotate-gradient 8s linear infinite;
background-image: linear-gradient(
var(--hero-angle),
oklch(55% 0.2 var(--hero-hue)),
oklch(40% 0.25 calc(var(--hero-hue) + 60))
);
}
@keyframes rotate-gradient {
from {
--hero-angle: 0deg;
--hero-hue: 240;
}
to {
--hero-angle: 360deg;
--hero-hue: 300;
}
}
/* ── Per-element progress bar without JavaScript ─────────────────────────── */
/* Each .progress-bar element has its own --card-progress (inherits: false) */
.progress-bar {
/* The data attribute value is injected via PHP inline style: */
/*