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.
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-imageignoresborder-radius; the border stays rectangular even when the element has rounded corners.- When
border-image-sliceis set tofill, the image paints on top of thebackground, 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.



