Market
Pricing
Sign inStart
Field GuideCSS and web
CSS and web

CSS @property: animating gradients and custom properties

Gradients refuse to transition because the browser cannot blend one image into another. Give a custom property a type and that limit disappears. Here is how @property works, with code you can paste.

GradientlyVerified Gradiently account·October 1, 2026·6 min read
Cover: Hollow Aria · GR·QW8R·QX

The short version

  • CSS @property registers a custom property with a syntax, an inheritance rule and an initial value, which tells the browser what type of value it holds.
  • Browsers cannot transition a gradient directly, because background-image is not interpolable, but they can transition a typed custom property used inside the gradient.
  • Typed properties such as <color>, <angle>, <percentage> and <length> animate smoothly; a property with the universal * syntax only flips between values.
  • The @property rule is supported in current Chrome, Edge, Safari and Firefox, and browsers that ignore it simply show the gradient without motion.
  • Animating a custom property inside a background repaints the element on every frame, so keep the animated area modest and respect prefers-reduced-motion.
On this page
Why gradients will not transition on their ownThe @property syntaxAnimate a gradient colour on hoverRotate a conic gradient borderMove a colour stopRegistering from JavaScriptBrowser support, fallbacks and performance

CSS @property registers a custom property with a type, so the browser knows that --angle holds an angle or --tint holds a colour. Once it knows the type, it can interpolate between two values, which means you can transition and animate things CSS normally refuses to move: gradient colours, gradient angles and the position of a colour stop. Without registration, a custom property is just a string, and a string can only jump from one value to the next.

Why gradients will not transition on their own

Try transition: background 0.4s on a button whose hover state swaps one linear-gradient() for another and nothing eases: the new gradient snaps in. Gradients are images, and the specification treats background-image as not interpolable, so the browser has no way to blend one image into a different one.

The usual workaround is to oversize the background and slide background-position, which is how most animated CSS gradients are built. It works, but it only moves a fixed gradient around. You cannot change a colour, rotate an angle or push a stop along. Registered properties change that, because the thing being animated is no longer the image but a typed number or colour inside it. The browser recalculates the gradient on every frame from the current value.

Unregistered --tint

  • Stored as a string of tokens
  • Transitions jump halfway through
  • Inherits by default
  • An invalid value breaks the declaration at computed time

Registered with @property

  • Parsed as a real colour
  • Transitions interpolate smoothly
  • Inheritance is your choice
  • An invalid value falls back to the initial value

The @property syntax

An @property rule has three descriptors. syntax says what type of value is allowed, inherits says whether children receive the value, and initial-value is used when nothing else sets it. All three are required, except that initial-value may be left out when the syntax is *. If any required part is missing or wrong, the browser ignores the whole rule, silently.

css
@property --tint {
  syntax: '<color>';
  inherits: false;
  initial-value: #7c3aed;
}

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

@property --stop {
  syntax: '<percentage>';
  inherits: false;
  initial-value: 40%;
}
Three registered properties: a colour, an angle and a stop position. Register them once, at the top level of a stylesheet.

The initial value must be computationally independent, which in practice means absolute units: 0deg, 40%, 12px and hex colours are fine, while 2em or var(--x) are not. Setting inherits: false is usually what you want for animation, and it spares the browser from pushing the value down the tree.

<color>

Animates smoothly

Yes

Good for

Gradient colours, theme shifts
<angle>

Animates smoothly

Yes

Good for

Linear angles, conic rotation
<percentage>

Animates smoothly

Yes

Good for

Colour stop positions, sizes
<length>

Animates smoothly

Yes

Good for

Radial sizes, offsets
<number>

Animates smoothly

Yes

Good for

Opacity style values, multipliers
<integer>

Animates smoothly

Yes, in whole steps

Good for

Counters, stepped effects
*No, it flips

Good for

Anything you never animate
SyntaxAnimates smoothlyGood for
<color>YesGradient colours, theme shifts
<angle>YesLinear angles, conic rotation
<percentage>YesColour stop positions, sizes
<length>YesRadial sizes, offsets
<number>YesOpacity style values, multipliers
<integer>Yes, in whole stepsCounters, stepped effects
*No, it flipsAnything you never animate
The syntaxes that matter for gradient work. You can also accept several, such as '<length> | <percentage>', or a list with +.

Animate a gradient colour on hover

This is the smallest useful example. The button uses --tint as its second colour, and the hover state only changes --tint. Because the property is registered as a colour, the transition eases between violet and coral instead of snapping.

css
@property --tint {
  syntax: '<color>';
  inherits: false;
  initial-value: #7c3aed;
}

.button {
  background: linear-gradient(120deg, #1e1b4b 0%, var(--tint) 100%);
  transition: --tint 400ms ease;
}

.button:hover {
  --tint: #fb7185;
}
The transition names the custom property itself. Writing transition: background would do nothing here.
Three frames of the same button. The middle colour only exists because the browser can interpolate a registered colour.

For more ways to treat buttons, see gradient button CSS and CSS gradient hover effects.

Rotate a conic gradient border

A favourite use of @property is a border that seems to travel around a card. A conic gradient starts from an angle, so registering that angle and animating it from 0deg to 360deg spins the colours. Two backgrounds do the work: the card colour clipped to the padding box, and the conic gradient clipped to the border box.

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

.card {
  border: 2px solid transparent;
  border-radius: 16px;
  background:
    linear-gradient(#0f0b1e, #0f0b1e) padding-box,
    conic-gradient(from var(--angle), #22d3ee, #7c3aed, #f472b6, #22d3ee) border-box;
  animation: spin 6s linear infinite;
}

@keyframes spin {
  to { --angle: 360deg; }
}

@media (prefers-reduced-motion: reduce) {
  .card { animation: none; }
}
Repeat the first colour at the end so the seam where 360deg meets 0deg disappears.
The conic ring at 0degconic-gradient(from 0deg, #22d3ee, #7c3aed, #f472b6, #22d3ee)
The same gradient the border uses. As --angle grows, this ring turns, and the thin strip you see around the card travels with it. Gradient borders in CSS covers the static version.

Move a colour stop

A registered percentage lets a stop slide. Use it for a progress fill, a highlight that sweeps across a heading, or a horizon that rises on scroll. Here --stop moves the point where the gradient turns from ink to teal.

css
@property --stop {
  syntax: '<percentage>';
  inherits: false;
  initial-value: 20%;
}

.meter {
  background: linear-gradient(90deg, #0d9488 0%, #0d9488 var(--stop), #0f172a var(--stop));
  transition: --stop 600ms ease-out;
}

.meter[data-done] {
  --stop: 100%;
}
Two stops at the same position make a hard edge, and the edge moves as --stop changes.

Registering from JavaScript

CSS.registerProperty() does the same job from a script. It is handy when a design system registers its properties at runtime, or when a value only becomes known after load. Registering the same name twice throws an error, so guard it.

js
if ('registerProperty' in CSS) {
  try {
    CSS.registerProperty({
      name: '--angle',
      syntax: '<angle>',
      inherits: false,
      initialValue: '0deg',
    })
  } catch {
    // Already registered, which is fine.
  }
}
The JavaScript form uses initialValue in camel case. Everything else matches the CSS rule.

Browser support, fallbacks and performance

@property works in current Chrome, Edge, Safari and Firefox; Firefox was the last to add it, in version 128. Check caniuse if you support older devices. A browser that does not understand the rule treats --angle as an ordinary custom property, so your gradient still draws at its starting value and simply does not move. That is a fine fallback, provided the static frame looks finished.

Mind the repaint

Animating a property used in background makes the browser repaint that element on every frame. A small border or button is cheap; a full screen hero animated forever costs battery. CSS gradient performance has the measurements to watch.

  1. 1

    Register before you use

    Put every @property rule at the top level, outside media queries, so it applies everywhere.

  2. 2

    Use typed syntaxes

    Choose <color>, <angle> or <percentage>. Avoid * for anything you plan to animate.

  3. 3

    Transition the property

    Write transition: --tint 400ms, naming the custom property rather than background.

  4. 4

    Respect reduced motion

    Stop looping animations under prefers-reduced-motion: reduce, as described in prefers reduced motion.

  5. 5

    Check the still frame

    Turn the animation off and make sure the resting gradient still looks intentional.

If the motion you want lives on social posts or video rather than a web page, a Gradiently Mark already carries its own light and slow motion, and Pro exports it as MP4, WebM or GIF where the browser supports recording, without writing keyframes. For everything on the web, @property is the cleanest tool you have. The MDN reference lists every descriptor.

Design with a living gradient

Posts, stories, slides and banners in every size, with text that always reads.

Start designing, free

Questions people ask

What does CSS @property do?

It registers a custom property with a type, an inheritance rule and an initial value. Knowing the type lets the browser validate the value and interpolate it in transitions and animations.

Can you animate a CSS gradient?

Not directly, because background-image is not interpolable. Register the colours, angle or stop positions with @property, use them inside the gradient, and animate those properties instead.

Why is my @property animation not working?

Usually a descriptor is missing, the initial value uses a relative unit, the syntax is *, or the transition names background instead of the custom property. Any invalid descriptor makes the browser ignore the whole rule.

Does Firefox support @property?

Yes. Firefox added @property in version 128, so it now works in every major current browser.

Should inherits be true or false?

Use false for most animated values. It keeps the value on the element where you set it and avoids extra work for the browser.

Share this guide

Written by GradientlyVerified Gradiently account

The team behind Gradiently, a design tool built around Marks: living gradients that make everything you design look like yours.

See our profile

On this page

Why gradients will not transition on their ownThe @property syntaxAnimate a gradient colour on hoverRotate a conic gradient borderMove a colour stopRegistering from JavaScriptBrowser support, fallbacks and performance

Share this guide

Next in CSS and web

Tailwind gradient text: classes, v4 syntax and fixes

Keep reading

All CSS and web guides
CSS and web

CSS animated gradient backgrounds, done properly

4 min read

Gradients cannot be animated directly, but everything around them can. Four techniques, what each costs the browser, and how to keep motion calm and optional.

CSS and web

CSS conic gradient: wheels, pie charts and sheen

4 min read

conic-gradient() sweeps colour around a point like the hand of a clock. That one idea draws colour wheels, pie charts, donut charts, metallic sheen and starbursts.

CSS and web

CSS gradient border: four ways to draw one

5 min read

border-color does not take a gradient, so every gradient outline is a workaround. Here are the four that matter, what each can and cannot do, and which to reach for.

Find the look that’s only yours.

Every Mark is a living background with one owner. Try an unclaimed Mark, and claim yours when it feels right.

Start designing

Check a certificate

Type a certificate number or a Mark’s code to see who owns it.

Works with ChatGPT and Claude

Ask your AI for a design. It makes it in your Mark, ready to edit.

ChatGPTClaude
How to connect

Product

  • Market
  • Explore
  • Pricing
  • Certificates

Learn

  • Field Guide
  • Help centre
  • What’s new

Developers

  • Overview
  • ChatGPT and Claude
  • API reference
  • API keys

Company

  • About
  • Contact us
  • Sign in

Legal

  • Terms
  • Privacy
  • Refunds
  • Mark protection
  • Cookies
  • All policies
Move through the letters. Each one wears a real Mark.Touch a letter to see its Mark.One name. Ten Marks inside it.
© 2026 Gradiently