Animated gradient border in CSS, Tailwind and React

A pure CSS conic border, or a live GPU border with a transparent inside, for cards and buttons.

There are two good ways to draw an animated gradient border. For a simple spinning ring around a solid card, a few lines of CSS with a conic gradient and @property are enough. For a border that flows like the rest of a live gradient, keeps the inside transparent and follows your theme, install the free Gradient Border component:

npx shadcn@latest add https://gradients.sh/r/gradient-border.json

Pro plan

Every component and preset.

The CSS way: a rotating conic gradient

This works in any stack, including plain HTML. Register an angle with @property so the browser can animate it, paint the card's fill on the padding-box and the gradient on the border-box, then rotate the gradient:

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

.gradient-border {
  border: 2px solid transparent;
  border-radius: 16px;
  background:
    linear-gradient(var(--card, #fff), var(--card, #fff)) padding-box,
    conic-gradient(
        from var(--border-angle),
        #7c5cff,
        #38d6f5,
        #ff5ca8,
        #ffb36b,
        #7c5cff
      )
      border-box;
  animation: border-spin 4s linear infinite;
}

@keyframes border-spin {
  to {
    --border-angle: 360deg;
  }
}

@media (prefers-reduced-motion: reduce) {
  .gradient-border {
    animation: none;
  }
}

In a Tailwind 4 project the CSS goes in your main stylesheet as is, and you add the class next to your utilities: <div className="gradient-border p-6">. Without @property, the angle can't interpolate and the gradient would jump instead of turning.

The catch is the fill. The padding-box layer paints the inside, so it needs a solid colour: your --card token, or white when the page has none. A translucent card or one over an image shows the gradient through it. The motion is also only a rotation, the same colours chasing each other around.

The React way: a live gradient border

The Gradient Border component draws a real gradient on a canvas behind its children, then masks it to a ring with mask-composite: exclude. The inside stays transparent, so the border works on glass cards, over images and on top of another gradient.

1. Install

You need a React project with a components.json. If you don't have one, run npx shadcn@latest init first. Next.js, Vite, React Router and TanStack Start all work.

pnpm dlx shadcn@latest add https://gradients.sh/r/gradient-border.json

The CLI copies components/gradient-border.tsx, the shared components/gradient-surface.tsx and the engine in lib/gradient-engine/ into your project. You own the code.

2. Wrap your content

components/pro-card.tsx
import { GradientBorder } from "@/components/gradient-border";

export function ProCard() {
  return (
    <GradientBorder radius={16} borderWidth={2} className="w-80">
      <div className="rounded-[14px] bg-card p-6">
        <p className="font-medium">Pro plan</p>
        <p className="mt-1 text-sm text-muted-foreground">
          Every component and preset.
        </p>
      </div>
    </GradientBorder>
  );
}

The component adds borderWidth as padding, so set the child's radius to radius minus borderWidth for corners that line up. Give the child its own background and padding. Leave the background off when you want the inside to see through.

3. Pick the look

The border runs a linear flow by default: four bright colours (#7c5cff, #38d6f5, #ff5ca8, #ffb36b) drifting quickly, with no grain and no pointer response. The props that matter most here:

PropDefaultWhat it does
borderWidth1.5Ring thickness in pixels.
radius16Corner radius in pixels.
kind"linear-flow"The gradient inside the ring: "mesh", "aurora", "waves" and more.
colors4 coloursTwo to six colours, any CSS colour.
speed1.20 to 2. How far the gradient travels per loop.
loopSecondsfrom kind4 to 30. The motion repeats exactly every loopSeconds.

The full list is in the Gradient Border props table.

4. Use your theme colours

Every colour prop takes CSS variables, so the border can use your shadcn tokens:

<GradientBorder colors={["var(--primary)", "var(--chart-2)", "var(--chart-4)"]}>
  {children}
</GradientBorder>

When the class on <html> changes, the component re-reads the variables, so dark mode switches the border too. Theming explains the details.

Which one to use

NeedUse
A spinning ring on a solid card, no JavaScriptCSS conic + @property
Plain HTML, Vue, Svelte or a site builderCSS conic + @property
A transparent or glass insideGradient Border
Flowing colour that matches a live backgroundGradient Border
Colours from CSS variables that follow dark modeEither; the component re-reads them for you

Performance

The CSS version costs almost nothing: the browser repaints one gradient per frame.

The component draws on the GPU (WebGPU, with WebGL2 as the fallback). The first gradients.sh component adds about 50 KB of gzipped JavaScript with the engine; each one after that adds a few KB. A thin ring doesn't need detail, so the border caps its canvas at 1× device pixels unless you pass maxDpr. Like every gradients.sh component, it stops drawing off screen and in hidden tabs, and shows one still frame to visitors who prefer reduced motion.

For a moving background behind the card as well, see Animated gradient background for shadcn/ui or the aurora background guide, or tune a gradient in the editor and install what you see.