Three ways to animate CSS borders without the jank

Animating a CSS border directly is a well-known pain point: the property isn't animatable in a smooth way, so you end up with jumps between values. The standard workarounds all revolve around faking the border with other properties that do animate cleanly. Here's a rundown of the viable approaches and what each one costs you.

The three methods for drawing a border in CSS are border, outline, and box-shadow. For animation purposes, a real border is usually the worst choice. A common alternative is to inject generated content via ::before and ::after, creating a faux border that you can then animate freely.

Border Animations using generated content by Coco

The trouble with the generated-content trick is that it breaks the box model. The faux border is painted underneath the element's content, so the content can obscure it. The typical fix is to re-apply the intended border width as padding, but that's a manual adjustment that gets brittle fast.

Keeping the box model intact

A cleaner approach preserves the box model by using multiple backgrounds stretched into the border area. Start with a dotted border and layer backgrounds on top of it. By default, background images are painted relative to the padding-box, so they don't extend into the border region. Without correction, a conic-gradient will look wrong because it gets repeated inside the border instead of spanning it.

Fixing background placement

The fix is to set background-origin to border-box, which makes the background size itself against the full border box rather than the padding box:

/* Manually add or offset the size of the border where needed */
background-position: calc(var(--border-size) * -1) calc(var(--border-size) * -1);
background-size: calc(var(--border-size) * 2 + 100%) calc(var(--border-size) * 2 + 100%);

With this single addition, the gradient spans the border properly.

Now that the backgrounds occupy the entire space, you need to shrink the semi-transparent layer back so it doesn't sit underneath the border. Instead of adjusting background-size, set background-clip to padding-box. This stops the background from being drawn under the border area:

background-clip:
  padding-box, /* Clip white semi-transparent to the padding-box */
  border-box /* Clip colored boxes to the border-box (default) */
;

Finally, make the border transparent so the gradient shows through:

border: 0.3rem dotted transparent;

Animating the result

To animate the border, manipulate the start angle of the conic-gradient:

--angle: 0deg;
conic-gradient(
  from var(--angle),
  #d53e33 0deg 90deg,
  #fbb300 90deg 180deg,
  #377af5 180deg 270deg,
  #399953 270deg 360deg
);

Where @property is supported, you can register the angle as a custom property, which the browser can then interpolate smoothly between values. This keeps the animation declarative and simple:

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

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

The border-image shortcut

You can also use border-image to draw a gradient border. Browser support is solid — it's available in Chrome 16+, Firefox 15+, and Safari 6+. This approach yields more compact code since you skip the background layering entirely, and animation works the same way:

/* Create a border */
border: 0.5rem solid transparent;

/* Paint an image in the border */
border-image:
  conic-gradient(
    from var(--angle),
    #d53e33 0deg 90deg,
    #fbb300 90deg 180deg,
    #377af5 180deg 270deg,
    #399953 270deg 360deg
  ) 1
;

But it comes with two significant constraints:

  • border-image ignores border-radius; the border stays rectangular even when the element has rounded corners.
  • When border-image-slice is set to fill, the image paints on top of the background, not underneath it. That becomes a problem if you want a semi-transparent background.

Trade-offs in practice

All three approaches are valid, and the right pick depends on whether you can tolerate a broken box model, need rounded corners, or want a semi-transparent element background. The background-based method keeps the most native behavior, while border-image is simplest when you don't need curves. Generated content remains a fallback where these constraints don't matter.