EdgeCases Logo
Feb 2026
CSS
Surface
6 min read

CSS @property: Finally Animating the Un-animatable

Declare custom property types with @property to unlock gradient, color, and numeric animations

css
@property
custom-properties
animation
gradients
houdini

CSS custom properties (variables) can't animate by default. Set --color from red to blue, and it jumps instantly—no transition. The @property rule changes this by declaring the variable's type, enabling smooth interpolation.

Why Variables Don't Animate

CSS custom properties are strings. The browser doesn't know if --foo: 10 is a number, length, angle, or just text. Without type information, interpolation is impossible—how do you animate between "red" and "blue" as strings?

:root {
  --bg-color: red;
  transition: --bg-color 0.5s; /* Does nothing! */
}

.element {
  background: var(--bg-color);
}

:root:hover {
  --bg-color: blue; /* Instant jump, no transition */
}

@property: Typed Custom Properties

The @property rule registers a custom property with explicit type information. The browser can now interpolate values:

@property --bg-color {
  syntax: '<color>';
  inherits: false;
  initial-value: red;
}

.element {
  background: var(--bg-color);
  transition: --bg-color 0.5s;
}

.element:hover {
  --bg-color: blue; /* Smooth transition! */
}

The syntax descriptor tells the browser this is a color. Now it knows to interpolate through color space, just like background-color would.

Available Syntax Types

  • <color> — any valid CSS color
  • <length> — px, rem, em, etc.
  • <percentage> — 0% to 100%
  • <number> — unitless numbers
  • <integer> — whole numbers only
  • <angle> — deg, rad, turn
  • <length-percentage> — length or percentage
  • <transform-function> — rotate(), scale(), etc.
  • <custom-ident> — keyword identifiers
  • * — any value (default, no interpolation)

The Killer Use Case: Gradient Animation

background: linear-gradient() can't animate—the gradient is a single computed image. But you can animate the values inside the gradient:

@property --gradient-angle {
  syntax: '<angle>';
  inherits: false;
  initial-value: 0deg;
}

@property --gradient-start {
  syntax: '<color>';
  inherits: false;
  initial-value: #ff6b6b;
}

@property --gradient-end {
  syntax: '<color>';
  inherits: false;
  initial-value: #4ecdc4;
}

.gradient-box {
  background: linear-gradient(
    var(--gradient-angle),
    var(--gradient-start),
    var(--gradient-end)
  );
  transition: 
    --gradient-angle 1s,
    --gradient-start 1s,
    --gradient-end 1s;
}

.gradient-box:hover {
  --gradient-angle: 180deg;
  --gradient-start: #a8e6cf;
  --gradient-end: #fdcb6e;
}

The gradient itself doesn't transition—but each custom property inside it does, creating the effect of an animating gradient.

Animating with @keyframes

Typed properties work with keyframe animations too:

@property --hue {
  syntax: '<number>';
  inherits: false;
  initial-value: 0;
}

@keyframes rainbow {
  to {
    --hue: 360;
  }
}

.rainbow-text {
  color: hsl(var(--hue), 80%, 50%);
  animation: rainbow 5s linear infinite;
}

Required Descriptors

All three descriptors are mandatory:

@property --my-prop {
  syntax: '<length>';     /* Required: the type */
  inherits: true;          /* Required: does it inherit? */
  initial-value: 0px;      /* Required: default value */
}

inherits controls whether child elements inherit the property. For animation purposes, false is usually what you want—each element manages its own value.

Browser Support Caveat

@property works in all modern browsers (Chrome 85+, Firefox 128+, Safari 15.4+). For older browsers, the animation simply won't happen—the property falls back to instant changes. This is progressive enhancement at its best.

/* Fallback: still works, just no animation */
:root {
  --accent: blue;
}

/* Enhancement: animates in supporting browsers */
@property --accent {
  syntax: '<color>';
  inherits: false;
  initial-value: blue;
}

JavaScript Registration Alternative

You can also register properties via JavaScript with CSS.registerProperty():

CSS.registerProperty({
  name: '--my-color',
  syntax: '<color>',
  inherits: false,
  initialValue: 'red'
});

// Useful for dynamic registration
// Same capabilities as @property

This is part of the CSS Houdini specification. Use it when you need runtime registration or when generating property names dynamically.

The Takeaway

@property turns custom properties from dumb strings into typed, animatable values. Gradients, complex color schemes, numeric values—anything that couldn't transition before can now animate smoothly. It's one of the most powerful CSS features you're probably not using yet.

Advertisement

Related Insights

Explore related edge cases and patterns

CSS
Expert
CSS @property Animation: The Compositor Can't Help You
7 min
TypeScript
Expert
TypeScript Performance: Recursive Types & Build Times
6 min
Performance
Deep
The Web Animation Performance Tier List
6 min

Advertisement