Skip to content

Border spin ​

Animated rotating gradient borders. Add border-spin to any element with a conic border gradient to spin it continuously.

Browser support

The animated angle needs @property, which is the older of the two, but there is nothing to spin without a border gradient, so the gradient's floor is the one that applies. Live status for both is at the foot of this page.

This animation does not stop itself

border-spin carries no prefers-reduced-motion query of its own, so a reader who has asked their system for reduced motion still gets the spin. Ask for it through the variant instead:

html
<div class="border-4 border-conic-0 motion-safe:border-spin"></div>

motion-reduce:animate-none turns it off the other way round. Neither needs !, and every other variant works the same: border-spin declares nothing but the animation, so it has no specificity to out-rank.

Quick reference ​

class styles
border-spinanimation: border-spin var(--jib-border-spin-duration) linear infinite var(--jib-border-spin-direction)
border-spin-reverse--jib-border-spin-direction: reverse
border-spin-duration-<number>--jib-border-spin-duration: <number> * 1000ms
border-spin-duration-<name>--jib-border-spin-duration: var(--jib-border-spin-duration-<name>)
border-spin-duration-[<value>]--jib-border-spin-duration: <value>

Basic usage ​

Add border-spin to rotate the gradient continuously. The default duration is 1s.

border-spin
border-spin border-spin-duration-2
border-spin-duration-1.5

Reverse ​

border-spin-reverse turns the rotation the other way. It declares only the direction, so it composes with everything else: pair it with a duration, a variant, or neither.

border-spin
border-spin border-spin-reverse
border-spin-reverse border-spin-duration-3

Two rings turning against each other read as a single mechanism rather than two spinners:

Spin duration ​

Use border-spin-duration-<number> where n is in seconds (multiplied by 1000ms):

border-spin-duration-0.5
1s (default)
border-spin-duration-3

Using a custom value ​

Use bracket notation for custom durations:

border-spin-duration-[500ms]

Naming your own durations ​

border-spin-duration-* reads the --jib-border-spin-duration-* theme namespace, so a project can name its speeds once and use them everywhere:

css
@theme {
  --jib-border-spin-duration-slow: 4s;
  --jib-border-spin-duration-brisk: 600ms;
}
html
<div class="border-4 border-conic-0 border-spin border-spin-duration-slow"></div>

Using a custom variable ​

Reference CSS custom properties with the typed bare-value syntax (type:--var):

UtilityType hintExample
border-spin-duration-*timeborder-spin-duration-(time:--spin-speed)

Custom properties inherit, so the variable can live on an ancestor and every spinner below it follows. Changing it at runtime retimes them together:

html
<div style="--spin-speed: 2.5s">
  <div class="border-4 border-conic-0 border-spin border-spin-duration-(--spin-speed)"></div>
  <div class="border-4 border-conic-0 border-spin border-spin-duration-(--spin-speed)"></div>
</div>

How it works ​

Registering --jib-border-gradient-angle as an <angle> through @property is what makes it animatable. Every conic border gradient starts from that angle, so border-conic-45 is a declaration of it, and a @keyframes animation rotates it from 0deg to 360deg. An animation beats a declaration by cascade origin rather than by selector weight, which is how border-spin takes the angle over while carrying no specificity of its own. --jib-border-spin-duration sets how long one turn takes, defaulting to 1s, and --jib-border-spin-direction is the animation-direction slot of the same shorthand, defaulting to normal. Because both are read out of the shorthand rather than redeclared beside it, border-spin-reverse and the duration utilities never restate the animation and so never have to out-rank it.

Import ​

Included in @import 'tw-jib-css'. To import individually:

css
@import 'tw-jib-css/border-gradient';

Note: border-spin is part of the border-gradient module.

Baseline status

What these utilities are built on, live from the Web Platform Dashboard.