@property

Animatable Custom Properties

A plain custom property is just text to the browser. It has no idea whether --angle holds an angle, a color or a length, so it can’t blend between two values. @property gives it a type, and a transition on that property finally works.

Typed --angle-typed

Plain --angle-plain

Typed --tint-typed

Plain --tint-plain

Typed --fill-typed

Plain --fill-plain

The CSS

Copied
/* @property registers a property NAME page-wide, so each typed/plain pair
   below needs two different custom properties, not two uses of one. */
@property --angle-typed {
  syntax: '<angle>';
  inherits: false;
  initial-value: 0deg;
}

.dial-typed {
  --angle-typed: 0deg;
  background: conic-gradient(from var(--angle-typed), var(--accent), var(--accent-tertiary));
  transition: --angle-typed 0.6s; /* eases: --angle-typed has a registered <angle> type */
}

/* Same idea, typed as a <color> instead of an <angle>. */
@property --tint-typed {
  syntax: '<color>';
  inherits: false;
  initial-value: #F0748A;
}

.swatch-typed {
  --tint-typed: #F0748A;
  background: var(--tint-typed);
  transition: --tint-typed 0.6s; /* blends smoothly between two colors */
}

/* And as a <percentage>, driving a width. */
@property --fill-typed {
  syntax: '<percentage>';
  inherits: false;
  initial-value: 0%;
}

.bar-typed {
  --fill-typed: 0%;
  width: var(--fill-typed);
  transition: --fill-typed 0.6s; /* grows/shrinks instead of jumping */
}

/* Every "-plain" counterpart above uses the identical CSS with an
   unregistered property name, so none of it interpolates. */

How it works

Each row runs the same experiment with a different type: an <angle> for the dial, a <color> for the swatch, a <percentage> for the bar. The left one is registered with @property, so it eases. The right one is a plain custom property with otherwise identical CSS, so it jumps partway through instead.

Each pair uses two different property names on purpose. @property registers a name for the whole page, not one element, so sharing a name would make both sides animate.

@property also takes inherits and initial-value, so a registered property behaves like a built-in one: a type, a default, and something the browser can animate. That’s where animated gradients, colors and counters come from, with no JavaScript anywhere.

Widely supported

Chrome / Edge85+Safari16.4+Firefox128+
:has() · tic-tac-toe

turns, wins and reset, all CSS

@property · counter()
hover me
cos() · sin()

trigonometry, in CSS

:has() quantity query

switch guests on