← Back to CSS Course | Chapter 9: Modern CSS Features | Lesson 13 of 25

CSS @property

Think of a plain sticky note that just says 10 with no context -- is that dollars, degrees, or a page number? Now imagine a smart label maker that stamps 'THIS IS A NUMBER, DEFAULT 0, DO NOT INHERIT' right onto the note. @property is that label maker for CSS custom properties: it gives a --variable a real, enforced type instead of leaving it as untyped text.
Syntax
css
@property --property-name {
  syntax: "<type>";
  inherits: true | false;
  initial-value: value;
}

The Problem With Plain Custom Properties

A plain --custom-property declared with var() has no type checking at all -- the browser treats its value as an opaque string substituted at use-time, which means it can't be smoothly animated (the browser doesn't know how to interpolate between two arbitrary strings) and typos or invalid values fail silently.

Note:
  • Accepted values:
  • name: <value> — accepts any token stream
  • var(--name) — substituted as-is at use time
  • var(--name, fallback) — fallback if not defined
  • transition: --name — an unregistered property switches abruptly instead of animating

Example: The Problem With Plain Custom Properties

css
<style>
.box { padding: 20px; color: white; background: var(--my-color); }
.box:hover { --my-color: blue; }
:root {
  --my-color: red;
}
.box {
  transition: --my-color 0.3s;
}
</style>
<div class="box">Hover: the color snaps, it does not animate</div>

Registering a Typed Property With @property

@property registers a custom property name with an explicit syntax (its type, like <length> or <color>), whether it inherits, and an initial-value -- turning what was an opaque string into a real, browser-understood typed value.

Note:
  • Accepted values:
  • syntax: '<color>' — required type descriptor
  • inherits: true | false — required inheritance flag
  • initial-value: <value> — required for non-universal types
  • @property --name — the rule name must start with two dashes

Example: Registering a Typed Property With @property

css
<style>
@property --my-color {
  syntax: '<color>';
  inherits: false;
  initial-value: red;
}
.box { padding: 20px; color: white; background: var(--my-color); transition: --my-color 0.5s; }
.box:hover { --my-color: blue; }
@property --my-color {
  syntax: '<color>';
  inherits: false;
  initial-value: red;
}
</style>
<div class="box">Registered typed property</div>

The syntax Descriptor

The syntax descriptor declares the accepted value type using predefined syntax component names like <color>, <length>, <percentage>, <number>, or <angle>.

An invalid value assigned to the property is rejected and the initial-value is used instead, unlike plain custom properties which accept anything.

Note:
  • Accepted values:
  • '<color>' — any CSS color
  • '<length>' — px, rem, em etc.
  • '<percentage>' — e.g. 50%
  • '<number>' | '<integer>' — unitless numbers, whole numbers only
  • '<angle>' — deg, rad, turn
  • '<length-percentage>' — a length or a percentage
  • '<color> | none' — alternatives separated by |
  • '<length>+' | '<length>#' — space or comma separated lists
  • '*' — any value (universal)

Example: The syntax Descriptor

css
<style>
@property --gap-size {
  syntax: '<length>';
  inherits: false;
  initial-value: 0px;
}
.box { display: flex; gap: var(--gap-size); transition: --gap-size 0.4s; }
.box:hover { --gap-size: 30px; }
.box { background: #cfe8ff; border: 2px solid #1e6fd9; padding: 10px; }
@property --gap-size {
  syntax: '<length>';
  inherits: false;
  initial-value: 0px;
}
</style>
<div class="box">Length-typed custom property</div>

Now It Can Be Smoothly Animated

Because a registered property has a real, interpolatable type, the browser can now animate it smoothly with transition or @keyframes -- this is the primary practical payoff of @property, unlocking animations that were impossible with a plain untyped custom property.

Note:
  • Accepted values:
  • transition: --my-color 0.3s — animates a registered property
  • animation on a registered property — interpolates by type
  • <color> — interpolates color values
  • <length> | <angle> | <number> — interpolate numerically
  • syntax: '*' — cannot be interpolated

Example: Now It Can Be Smoothly Animated

css
<style>
@property --angle {
  syntax: '<angle>';
  inherits: false;
  initial-value: 0deg;
}
.box {
  background: conic-gradient(red var(--angle), blue);
  transition: --angle 0.3s;
}
.box:hover {
  --angle: 180deg;
}
</style>
<div class="box">Now smoothly animatable</div>

The inherits and initial-value Descriptors

inherits (true/false) explicitly controls whether the property flows down to child elements, and initial-value sets the fallback used whenever the property is unset or assigned an invalid value.

Both descriptors are required for a valid @property rule, unlike the optional nature of plain custom property fallbacks in var().

Note:
  • Accepted values:
  • inherits: true — children inherit the value
  • inherits: false — children get the initial value
  • initial-value: <value> — the value used when the property is not set
  • syntax: '*' — initial-value may be omitted

Example: The inherits and initial-value Descriptors

css
<style>
.parent { --my-size: 24px; }
.child { font-size: var(--my-size); background: #cfe8ff; border: 2px solid #1e6fd9; padding: 10px; }
@property --my-size {
  syntax: '<length>';
  inherits: true;
  initial-value: 16px;
}
</style>
<div class="parent"><div class="child">Child inherits --my-size</div></div>
Live Example
Related Topics
{# common_mistakes/chapter_summary/browser_support: on Hindi pages the view already swaps in the hi_ translation fields (or blanks these out if untranslated), so this renders correctly for both languages without a lang_code check here. #}
Common Mistakes
  1. Forgetting the syntax descriptor, so the property is not registered.
  2. Missing the initial-value for a property with inherits, so the rule is invalid.
  3. Expecting a custom property to animate without registering it with @property.
Browser Support

@property is a newer feature: supported in Chrome 85+, Safari 16.4+ and Firefox 128+.

Login to run this code

C/C++/Java/PHP execution requires a free account. Your code is saved — you'll land right back in the editor after logging in.