In ancient Egypt, priests possessed secret knowledge of magical formulas that gave objects extraordinary properties. Each formula had a strictly defined type - it could work on colors, numbers, or dimensions, and improper use meant the magic wouldn't work. The
@property rule in CSS works on a similar principle - it allows you to formally register a custom CSS property, giving it a type, initial value, and inheritance rules. Thanks to this, the browser "understands" the meaning of your variable and can animate it.Regular CSS variables (
--name: value) are treated by the browser as raw text. The browser doesn't know whether --my-color is a color, number, or something else. The consequence is that they cannot be smoothly animated:1:root {
2 --gradient-start: gold;
3 --gradient-end: purple;
4}
5
6.box {
7 background: linear-gradient(var(--gradient-start), var(--gradient-end));
8 transition: --gradient-start 1s, --gradient-end 1s;
9}
10
11/* This WON'T work! The browser doesn't know these are colors */
12.box:hover {
13 --gradient-start: crimson;
14 --gradient-end: navy;
15}The browser sees a change from one text string to another and cannot calculate intermediate values. Instead of a smooth transition, the color will jump instantly.
The
@property rule registers a property with a full type definition:1@property --gradient-start {
2 syntax: "<color>";
3 inherits: false;
4 initial-value: gold;
5}
6
7@property --gradient-end {
8 syntax: "<color>";
9 inherits: false;
10 initial-value: purple;
11}Now the browser knows that
--gradient-start and --gradient-end are colors. It can calculate intermediate values and perform smooth animation!Each
@property declaration requires three mandatory descriptors:1@property --property-name {
2 syntax: "<type>"; /* What type the value is */
3 inherits: true | false; /* Whether descendants inherit */
4 initial-value: value; /* Default value */
5}| Syntax | Description | Example | |--------|-------------|---------| |
"<color>" | Any CSS color | gold, #d4af37, rgb(212,175,55) |
| "<length>" | Length with unit | 10px, 2rem, 50vh |
| "<number>" | Number without unit | 0, 1.5, 360 |
| "<percentage>" | Percentage | 50%, 100% |
| "<angle>" | Angle | 45deg, 0.5turn |
| "<integer>" | Integer | 0, 3, 100 |
| "<length-percentage>" | Length or percentage | 10px, 50% |
| "<custom-ident>" | Custom identifier | ease-in, forwards |true - descendants inherit the value (like regular Custom Properties)false - descendants do NOT inherit; each element uses initial-valueThis is the most spectacular application of
@property - animating properties that normally cannot be animated:1@property --hue {
2 syntax: "<number>";
3 inherits: false;
4 initial-value: 45;
5}
6
7.egyptian-glow {
8 --hue: 45;
9 background: linear-gradient(
10 135deg,
11 hsl(var(--hue), 80%, 50%),
12 hsl(calc(var(--hue) + 60), 70%, 40%)
13 );
14 transition: --hue 0.8s ease;
15}
16
17.egyptian-glow:hover {
18 --hue: 200;
19}Thanks to
@property, the browser knows that --hue is a number and can smoothly transition from 45 to 200, animating the entire gradient!1@property --angle {
2 syntax: "<angle>";
3 inherits: false;
4 initial-value: 0deg;
5}
6
7.rotating-border {
8 --angle: 0deg;
9 border: 3px solid transparent;
10 border-image: linear-gradient(
11 var(--angle),
12 #d4af37,
13 #1e3a5f,
14 #d4af37
15 ) 1;
16 animation: rotate-gradient 3s linear infinite;
17}
18
19@keyframes rotate-gradient {
20 to {
21 --angle: 360deg;
22 }
23}1@property --bg-color {
2 syntax: "<color>";
3 inherits: false;
4 initial-value: #d4af37;
5}
6
7.pharaoh-card {
8 background: var(--bg-color);
9 transition: --bg-color 0.5s ease;
10}
11
12.pharaoh-card:hover {
13 --bg-color: #1e3a5f;
14}1@property --progress {
2 syntax: "<percentage>";
3 inherits: false;
4 initial-value: 0%;
5}
6
7.pyramid-progress {
8 background: linear-gradient(
9 to right,
10 #d4af37 var(--progress),
11 #e8d7b0 var(--progress)
12 );
13 transition: --progress 1s ease;
14}
15
16.pyramid-progress.complete {
17 --progress: 100%;
18}1@property --color-a {
2 syntax: "<color>";
3 inherits: false;
4 initial-value: #d4af37;
5}
6
7@property --color-b {
8 syntax: "<color>";
9 inherits: false;
10 initial-value: #8b6914;
11}
12
13.magic-gradient {
14 background: linear-gradient(135deg, var(--color-a), var(--color-b));
15 transition: --color-a 0.6s ease, --color-b 0.6s ease;
16}
17
18.magic-gradient:hover {
19 --color-a: #1e3a5f;
20 --color-b: #30d5c8;
21}| Feature | Custom Properties | @property | |---------|------------------|-----------| | Declaration |
--name: value | @property --name { ... } |
| Type | None (text) | Defined (color, length...) |
| Animation | Not possible | Possible! |
| Inheritance | Always | Configurable |
| Default value | Via var(--name, fallback) | Via initial-value |
| Validation | None | Browser validates type |@property is supported since Chrome 85 (2020), Edge 85, Safari 15.4 (2022), and Firefox 128 (2024). You can safely use it in modern projects. For older browsers, the property will simply behave like a regular Custom Property - without animation, but with the correct value.1/* Fallback - always works */
2.element {
3 --my-color: gold;
4 background: var(--my-color);
5}
6
7/* @property - adds animation capability */
8@property --my-color {
9 syntax: "<color>";
10 inherits: false;
11 initial-value: gold;
12}@property is like a magical formula of Egyptian priests - it gives your CSS variables real power, allowing the browser to understand their meaning and create smooth, spectacular animations where it was previously impossible.