March 27, 20252 min read

Build an animated border with Tailwind CSS

Example of animated borders built with Tailwind CSS on a dark design, over a vibrant gradient background.

“Effects with Tailwind” series · 1 of 4

  1. Build an animated border with Tailwind CSS ← you are here
  2. Glassmorphism without wrecking legibility
  3. Scroll-driven animations
  4. View transitions in Astro

CSS can’t animate the border property natively. What we can do is fake it: take a div, give it an animated background, and put the content inside a child element with padding. That padding becomes the “thickness” of the border.

If you don’t spend much time in CSS or in frameworks like Tailwind, this might sound convoluted. It isn’t, and the payoff looks great.

Gradient types in CSS

To build the effect we need to know what CSS gives us to work with:

  • Linear gradient: blends along a straight line in whatever direction you choose.
  • Radial gradient: blends outward from a center point.
  • Conic gradient: sweeps around a center point, like a color wheel.

We’ll use the conic gradient, because it’s the one that can be made to spin.

Building the border

<div
  class="w-full max-w-lg  bg-conic/[from_0deg] from-white to-white dark:from-black via-green-400 dark:to-black  rounded-2xl p-px"
>
  <div class="p-10 rounded-2xl bg-transparent">
    <p class="text-white text-center font-semibold ">
      Parent container background
    </p>
  </div>
</div>

Parent container background




Give the child container a background of its own and the border appears:

<div
  class="w-full max-w-lg bg-conic/[from_0deg] from-white to-white dark:from-black via-green-400 dark:to-black rounded-2xl p-px"
>
  <!-- Parent container -->
  <div class="p-10 rounded-2xl dark:dark:bg-zinc-900 bg-mint-50">
    <!-- Child container -->
    <p class="text-white text-center font-semibold">
      Once the child container has a background, the border effect shows up
    </p>
  </div>
</div>

Once the child container has a background, the border effect shows up

Adding motion with @property

@property lets us declare a custom property that the browser knows how to animate:

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

Then we write the animation with @keyframes and register it in the Tailwind theme:

@theme {
  --animate-rotate-border: border-rotate 3s linear infinite;
  @keyframes border-rotate {
    to {
      --border-angle: 360deg;
    }
  }
}

Now we wire it into the parent container’s classes:

<div
  class="w-full max-w-lg bg-conic/[from_var(--border-angle)] from-white to-white dark:from-black via-green-400 dark:to-black animate-rotate-border rounded-2xl p-px"
>
  <div class="p-10 rounded-2xl bg-transparent">
    <p class="text-white text-center font-semibold">
      Animated parent container background
    </p>
  </div>
</div>

Animated parent container background


And here it is with a background behind the content

Controlling the border thickness

The padding is what sets the thickness, so that’s the knob to turn:

<div
  class="w-full max-w-lg bg-conic/[from_var(--border-angle)] from-white to-white dark:from-black via-green-400 dark:to-black animate-rotate-border rounded-2xl p-[3px]"
>
  <div class="p-10 rounded-2xl dark:bg-zinc-900 bg-zinc-50">
    <p class="text-white text-center font-semibold">
      Bumping the padding "thickens the border"
      <br />
      <code>p-[3px]</code>
    </p>
  </div>
</div>

Bumping the padding "thickens the border"
p-[3px]

Tweaking the gradient

Tailwind gives us control over where each color sits in the gradient:

  • from-* → the color it starts on.
  • via-* → the color in the middle.
  • to-* → the color it ends on.

And we can move those stops around:

<div
  class="w-full max-w-lg bg-conic/[from_var(--border-angle)] from-white to-white dark:from-black via-green-400 dark:to-black from-30% to-60% animate-rotate-border rounded-2xl p-px"
>
  <div class="p-10 rounded-2xl bg-transparent">
    <p class="text-white text-center font-semibold">
      Moving the color stops gives you a different effect
      <br />
      <code>from-30% to-60%</code>
    </p>
  </div>
</div>

Moving the color stops gives you a different effect
from-30% to-60%

The finished thing

A couple of small adjustments to wrap up: I’ll change the from and to colors for something that reads more naturally, and drop the padding back to 1px.

<div
  class="w-full max-w-lg bg-conic/[from_var(--border-angle)] from-green-200/20 via-green-400 to-green-200/20 from-30% to-60% animate-rotate-border rounded-2xl p-px"
>
  <div class="p-10 rounded-2xl dark:bg-zinc-900 bg-zinc-50">
    <p class="text-white text-center font-semibold">
      This is the final result
    </p>
  </div>
</div>

This is the final result

From here it’s a matter of playing with the gradient’s colors and positions until you land on an effect that fits your design.

The next part of the series covers another effect: glassmorphism without wrecking legibility.

Share