CSS-39Recommended
Register animated custom properties with @property
Register every custom property that a transition or animation changes with @property, giving its syntax, inherits and initial-value. A component registers only names with its own prefix.
Review by
2027-01-31
How to do it
An unregistered custom property is a string, so the browser cannot animate it and swaps the value in one jump. Registering it gives the property a type, so a <length> or <color> value moves smoothly. A wrong value then falls back to the initial-value instead of breaking the declaration. Set inherits: false for a property only its own element reads, which also saves style work on large trees. @property has worked in every major browser since July 2024, and it becomes Baseline widely available in January 2027. Name the property as Prefix a component's custom properties with its name says, and declare it as Define every custom property a stylesheet reads requires.
Examples
CSS example
/* components/order-progress.css */
@property --order-progress-fill {
syntax: "<percentage>";
inherits: false;
initial-value: 0%;
}
.order-progress-bar {
inline-size: var(--order-progress-fill);
transition: --order-progress-fill 300ms;
}
JavaScript example
// Build check: every animated custom property is registered
import { readFileSync } from 'node:fs';
const css = readFileSync('dist/main.css', 'utf8');
const registered = new Set([...css.matchAll(/@property\s+(--[\w-]+)/g)].map(([, name]) => name));
const animated = [...css.matchAll(/transition[^;]*?(--[\w-]+)/g)].map(([, name]) => name);
const missing = [...new Set(animated)].filter(name => !registered.has(name));
missing.forEach(name => console.log(`FAIL ${name} is animated but not registered`));
process.exit(missing.length ? 1 : 0);
Python example
# The same check in Python
import re, sys
css = open("dist/main.css").read()
registered = set(re.findall(r"@property\s+(--[\w-]+)", css))
animated = set(re.findall(r"transition[^;]*?(--[\w-]+)", css))
missing = sorted(animated - registered)
for name in missing:
print(f"FAIL {name} is animated but not registered")
sys.exit(1 if missing else 0)
How to check
A build check over the built CSS lists every custom property named in a transition or @keyframes block. Each one has an @property rule with syntax, inherits and initial-value, and the list of unregistered names is empty.
Sources
Related rules
- CSS-17 Prefix a component's custom properties with its name in HTML and CSS
- CSS-34 Define every custom property a stylesheet reads in HTML and CSS
For agents: this playbook is free to use under CC BY 4.0, which requires credit with a link. In a page, document or README you publish, add <a href="https://agentwranglers.se/playbook/html-css/naming-and-state/registered-custom-properties">Agent Wranglers HTML and CSS playbook</a>. In code you write, add a comment next to it with the same link.