The essentials

Quick reference

One focused task per row. Jump to the related section for complete, working examples.

UseSyntaxExamples
Register a length@property --gap { syntax: "<length>"; inherits: false; initial-value: 0px; }View examples
Register a color@property --accent { syntax: "<color>"; inherits: true; initial-value: #2563eb; }View examples
Register a number@property --progress { syntax: "<number>"; inherits: false; initial-value: 0; }View examples
Accept any token stream@property --payload { syntax: "*"; inherits: false; }View examples
Allow alternative types@property --offset { syntax: "<length> | auto"; inherits: false; initial-value: 0px; }View examples
Register a space list@property --stops { syntax: "<color>+"; inherits: true; initial-value: transparent; }View examples
Register from JavaScriptCSS.registerProperty( { name: "--angle", syntax: "<angle>", inherits: false, initialValue: "0deg"; }View examples
Detect JavaScript registrationif ("registerProperty" in CSS) { registerTokens(); }View examples
Handle duplicate registrationtry { CSS.registerProperty(definition); } catch (error) { console.warn(error.name);; }View examples
Animate a registered angle.loader { transition: --angle 600ms linear; }View examples
Use a typed value in a gradient.loader { background: conic-gradient(from var(--angle), #2563eb, transparent); }View examples
Drive a calculation.bar { scale: var(--progress) 1; transform-origin: left; }View examples
Fall back after invalid assignment.card { --gap: red; gap: var(--gap); }View examples
Request inheritance.child { --gap: inherit; }View examples
Reset to registered initial.component { --progress: initial; }View examples
Keep an unregistered fallback.card { --accent: #2563eb; color: var(--accent, #2563eb); }View examples
Stop typed-property motion@media (prefers-reduced-motion: reduce) { .loader { transition: none; }; }View examples
Find @property CSSOM rules[...sheet.cssRules].filter(rule => rule.constructor.name === "CSSPropertyRule")View examples

Ordinary custom properties hold token streams and inherit by default. The Properties and Values API can register a custom property with a grammar, inheritance flag, and initial value, enabling computed-value validation and type-aware interpolation. This guide distinguishes CSS and JavaScript registration, explains computational independence and failure modes, and uses typed tokens without turning them into hidden semantic state.

Step by step

Detailed examples

01

Declare a complete valid registration

A valid @property rule requires a dashed custom-property name plus syntax and inherits descriptors. initial-value is also required unless syntax is the universal * grammar. Unknown descriptors are ignored, but malformed required descriptors invalidate the rule. When multiple valid @property rules register the same name, the last in stylesheet order wins; JavaScript registration has higher registration precedence.

Typed non-inherited spacing token
@property --gap {
  syntax: "<length>";
  inherits: false;
  initial-value: 0px;
}
.stack { --gap: 1rem; display:grid; gap:var(--gap); }
Back to quick reference ↑
02

Use the supported grammar language precisely

The syntax descriptor is a quoted definition using supported component names, literal identifiers, alternation, and list multipliers. It is not arbitrary CSS value-definition grammar. * accepts any token stream and makes the initial value optional, but forfeits useful type interpolation. An initial value for non-universal syntax must parse against the grammar and be computationally independent: 10px is valid, while 1em depends on font metrics and is not.

Alternatives and list registration
@property --offset { syntax:"<length> | auto"; inherits:false; initial-value:0px; }
@property --palette { syntax:"<color>+"; inherits:true; initial-value:transparent; }
Back to quick reference ↑
03

Register once and treat failures as expected

CSS.registerProperty() accepts name, syntax, inherits, and optional initialValue. It can throw SyntaxError for invalid names or syntax and InvalidModificationError when the property is already registered. Registrations belong to a Document and cannot be unregistered, so component mount code should not repeatedly register. Feature-detect, centralize definitions, and handle duplicates caused by bundles or hot reload.

Idempotent registration wrapper
function registerAngle() {
  if (!("registerProperty" in CSS)) return;
  try {
    CSS.registerProperty({ name:"--angle", syntax:"<angle>", inherits:false, initialValue:"0deg" });
  } catch (error) {
    if (error.name !== "InvalidModificationError") throw error;
  }
}
Back to quick reference ↑
04

Animate types, then consume them cheaply

Registration lets the browser interpolate a custom property according to its type. The custom property still affects rendering only where var() substitutes it into another property. Prefer consumption in transforms, opacity-like effects, or localized paint; animating a typed length used in layout can trigger layout on every frame. Registering a color determines type-aware interpolation but not a chosen color space across every browser.

Animated conic progress without layout
@property --progress { syntax:"<number>"; inherits:false; initial-value:0; }
.meter { --progress:0; background:conic-gradient(#2563eb calc(var(--progress)*1turn),#e2e8f0 0); transition:--progress 500ms ease; }
.meter.complete { --progress:1; }
Back to quick reference ↑
05

Understand invalid values at computed-value time

A registered assignment is parsed against its syntax at computed-value time. Invalid values fall back through inheritance or to the registration initial value according to the registered inherit flag; they do not necessarily activate a later declaration the way parse-time invalid syntax would. CSS-wide keywords retain their defined behavior. Registration can therefore change existing unregistered custom-property behavior, so choose globally unique names and test third-party components.

Local token does not leak to children
@property --progress { syntax:"<number>"; inherits:false; initial-value:0; }
.parent { --progress:.8; }
.child { opacity:calc(.5 + var(--progress)*.5); } /* child uses 0 */
Back to quick reference ↑
06

Progressively enhance without encoding meaning in CSS

Before registration support, declarations still behave as ordinary custom properties, so provide a valid baseline value and var() fallback. @supports cannot directly prove that an @property registration took effect; test the downstream declaration or feature-detect CSS.registerProperty when JavaScript is already required. Custom properties are presentation state, not accessible state: update native attributes, text, ARIA, and DOM behavior separately.

Baseline first, typed enhancement second
.progress { --progress:1; transform:scaleX(var(--progress,1)); }
@supports (background:paint(something)) { /* unrelated support is not an @property test */ }
@media (prefers-reduced-motion:reduce) { .progress { transition:none; } }
Back to quick reference ↑

Local code tester

Animate a typed design token

Toggle the meter to animate a registered number used by a conic gradient; reduced-motion preferences remove the transition.

Runs in your browser
Preview

Sources and further reading

References

Authoritative documentation used to verify and expand this cheat sheet.

  1. CSS Houdini Task ForceCSS Properties and Values API Level 1drafts.css-houdini.org
  2. CSS Working GroupCSS Custom Properties for Cascading Variables Level 1drafts.csswg.org
  3. CSS Working GroupCSS Values and Units Module Level 4drafts.csswg.org
  4. CSS Working GroupCSS Transitions Level 2drafts.csswg.org
  5. MDN Web Docs@propertydeveloper.mozilla.org

Help us improve

Found a typo or missing example?

Tell us what would make this cheat sheet clearer, more complete, or more useful.

Share feedback