Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

Typed variables: @property

A regular CSS variable from the lesson "CSS variables" is just a piece of text. The browser does not know that --accent holds a color and --angle an angle, so it cannot change their values smoothly. The @property rule registers a variable: it states its type, whether it is inherited and what it equals by default.

A variable without a type

A gradient cannot be animated directly, but you can change a color inside it through a variable. Hover over the button:

<style>
  .btn {
    --from: tomato;
    padding: 12px 24px;
    border: 0;
    color: white;
    background: linear-gradient(90deg, var(--from), gold);
    transition: --from 0.6s;
  }
  .btn:hover { --from: rebeccapurple; }
</style>
 
<button class="btn">Hover me</button>

The color changed with a jump, even though a transition is set: to the browser tomato and rebeccapurple are just two words with no in-between values.

The @property rule

Let us register --from as a color — and the same transition becomes smooth:

<style>
  @property --from {
    syntax: "<color>";
    inherits: false;
    initial-value: tomato;
  }
  .btn {
    padding: 12px 24px;
    border: 0;
    color: white;
    background: linear-gradient(90deg, var(--from), gold);
    transition: --from 0.6s;
  }
  .btn:hover { --from: rebeccapurple; }
</style>
 
<button class="btn">Hover me</button>

Now the browser knows it is a color and computes all the shades between the two values.

Three descriptors

DescriptorWhat it sets
syntaxthe value type in quotes
inheritstrue — children get the value from the parent, false — they do not
initial-valuethe value if the variable is not set anywhere

Types are written in angle brackets:

TypeExample value
<color>tomato, #ff6347
<length>12px, 2rem
<percentage>50%
<number>1.5
<angle>45deg, 0.5turn
*anything, like a regular variable

A rotating angle

An angle can be animated too. A conic gradient conic-gradient() paints a block in sectors around the center, like a clock face, and the word from sets the angle it starts at. Let us put the --angle variable there:

<style>
  @property --angle {
    syntax: "<angle>";
    inherits: false;
    initial-value: 0deg;
  }
  @keyframes spin {
    to { --angle: 360deg; }
  }
  .ring {
    width: 120px;
    height: 120px;
    border-radius: 50%;
    background: conic-gradient(from var(--angle), tomato, gold, seagreen, tomato);
    animation: spin 3s linear infinite;
  }
</style>
 
<div class="ring"></div>

It is not the block that rotates, only the gradient inside it. Shimmering button borders are made the same way.

Type checking

A registered variable does not accept a value of another type: the browser skips such a declaration and takes initial-value:

<style>
  @property --gap {
    syntax: "<length>";
    inherits: false;
    initial-value: 8px;
  }
  .box { margin: 4px 0; padding: var(--gap); background: lavender; }
  .big { --gap: 30px; }
  .wrong { --gap: red; }
</style>
 
<div class="box big">--gap: 30px</div>
<div class="box wrong">--gap: red — not a length, 8px is used</div>

@property is supported in all major browsers since 2024.

Common mistake

Leaving out initial-value or inherits. Without them the whole @property rule is invalid, the variable stays a regular one — and the animation jumps again:

<style>
  @property --from {
    syntax: "<color>";
    inherits: false;
  }
  .btn {
    --from: tomato;
    padding: 12px 24px;
    border: 0;
    color: white;
    background: linear-gradient(90deg, var(--from), gold);
    transition: --from 0.6s;
  }
  .btn:hover { --from: rebeccapurple; }
</style>
 
<button class="btn">A jump: no initial-value</button>

Only the * type may go without an initial value. For every other type write all three descriptors.

Thanks for reading!

Donate to the site — we work hard for you.