Why bother animating border-image at all?
Most devs treat border-image as that dusty CSS property from 2012 that nobody uses. Big mistake. It gives you image-based (or gradient-based) borders with zero extra DOM nodes and zero pseudo-elements — a huge win when your card component already has a ::before and ::after doing other work.
The catch? Border images don't follow border-radius. If you need a curved animated ring, you're better off with a mask approach. But if you want a sharp, efficient, tileable border animation that runs on a single element, border-image is genuinely hard to beat.
Let's build one from scratch.
The HTML
Nothing fancy here — just a card with some content:
<div class="card">
<div class="card__content">Bruce Wayne</div>
</div>
The visual (the character image) is set as a background, so let's jump into the CSS.

Base styles and the border-image setup
We start with the card dimensions and a background image, then layer in the border-image longhands one at a time so you can see exactly which property does what.
.card {
width: 150px;
aspect-ratio: 0.69;
position: relative;
background: center / 90% no-repeat;
background-image: url("batman.jpg");
/* 1. What paints the border: a linear gradient */
border-image-source: linear-gradient(-45deg, red var(--p, 0%), transparent 0%);
/* 2. How the gradient is carved into 9 slices */
border-image-slice: 1;
/* 3. Physical thickness of the border */
border-image-width: 5px;
/* 4. Push the border outward, away from the box */
border-image-outset: 5px;
}
Key mental model:
border-image-slice: 1means "cut the source image into 1px slices and stretch the middle." For a gradient this produces a smooth fill across the whole border.border-image-widthis what actually renders the border. Without it, nothing shows.border-image-outsetis your friend when the border clips too close to the content.
Registering a custom property so we can animate it
Here's the gotcha that trips people up: gradients are not animatable by default. You can't transition linear-gradient(red, blue) to linear-gradient(red, blue, green) — the browser has no idea how to interpolate the stop positions.
The fix is @property. Register a custom property with a typed syntax and now the browser knows how to tween it:
@property --p {
syntax: "<percentage>";
initial-value: 0%;
inherits: false;
}
Drop it into the gradient and transition it on hover:
.card {
border-image-source: linear-gradient(-45deg, red var(--p), transparent 0%);
transition: --p 0.6s ease-out;
}
.card:hover {
--p: 100%;
}
The red stop slides from 0% to 100%, giving the illusion that the border is drawing itself. That's the whole trick.

Leveling up: conic gradients and animated slices
Linear is just the warmup. Swap in a conic-gradient and animate the slice count too, and you get a spinning, tiled border that looks like a loading indicator on steroids.
/* Two more registered custom properties */
@property --n {
syntax: "<number>";
initial-value: 1;
inherits: false;
}
@property --a {
syntax: "<angle>";
initial-value: 0deg;
inherits: false;
}
.card {
border-image-source: conic-gradient(from var(--a), red var(--a), transparent 0%);
border-image-width: 5px;
border-image-slice: var(--n);
border-image-repeat: round; /* tile slices without clipping */
/* Be explicit about what transitions — good for perf */
transition-property: --n, --a;
transition-duration: 0.6s;
}
.card:hover {
--n: 20; /* deeper slices = visible tiling */
--a: 360deg; /* full rotation = border draws itself in */
}
Watch out for these
- No
border-radiussupport. If your design needs curved corners, this technique is the wrong tool. Use a mask or an SVG stroke instead. - Browser support for
@propertyis solid in Chrome/Edge/Safari 16.4+ and Firefox 128+, but always feature-detect with@supportsif you're targeting older Safari. border-image-sliceanimations can be janky on low-end mobile GPUs. Test on a real device, not just DevTools throttling.- Don't animate
border-image-width— it triggers layout. Stick to paint-only properties.

Where to go next
Once you're comfortable with single gradients, try layering two border-image-source gradients with mask to get multi-color sweeps, or combine this with @scroll-timeline for scroll-driven border animations.
For a deeper look at modern CSS feature techniques like this one, check out our Gemini CLI Plan Mode deep dive — same energy, different tool.
And if you're working with data-heavy UIs where the card content is dynamic, pair this with a solid understanding of Pandas loc vs iloc for DataFrame indexing so your backend stays as clean as your frontend.
Your homework: fork the demo, swap the gradient for a repeating radial-gradient, and see what happens. The results are usually surprising.
Source: css-tricks.com