Neon Gradient Glow Card with Flowing Border

Three dark cards on a black section, each outlined in a two-colour neon border. The colours slide along the border and a blurred copy of the same gradient sits behind the card, so the glow on the page shifts with it. The demo shows an event ticket, a now-playing card with moving level bars and a membership offer, each in its own colour pair. On phones the cards stack with room for the glow between them.

Code

// Adapted from Magic UI (https://magicui.design/docs/components/neon-gradient-card)
// Copyright (c) Magic UI. MIT License: https://we.inc/components/licenses
import type { CSSProperties, ReactNode } from "react";
import { motion, useReducedMotion } from "framer-motion";

const lineup = [
  { time: "20:00", act: "Saffron Tape" },
  { time: "21:15", act: "The Low Tide Choir" },
  { time: "22:40", act: "DJ Okonkwo-Reyes" },
];

const perks = ["Unlimited offline mixes", "Lossless audio on every device", "Early ticket access"];

const bars = [0.5, 0.9, 0.35, 0.75, 1, 0.6, 0.85, 0.4, 0.7, 0.55, 0.95, 0.45];

/**
 * A card with a two-colour border that flows up and down and throws the same colours as a glow.
 * `first` and `second` are any CSS colours. `radius` and `border` are in pixels.
 */
function NeonCard({ first, second, radius = 22, border = 2, className = "", children }: { first: string; second: string; radius?: number; border?: number; className?: string; children: ReactNode }) {
  return (
    <div
      className={`neon-gradient-glow-card-with-flowing-border relative isolate ${className}`}
      style={{ "--neon-a": first, "--neon-b": second, "--neon-border": `${border}px`, borderRadius: radius } as CSSProperties}
    >
      <div className="relative h-full bg-neutral-950 p-6 sm:p-7" style={{ borderRadius: radius - border }}>
        {children}
      </div>
    </div>
  );
}

export default function NeonGradientGlowCardWithFlowingBorder() {
  const reduce = useReducedMotion();
  const enter = (i: number) => ({
    initial: reduce ? false : { opacity: 0, y: 28, scale: 0.97 },
    animate: { opacity: 1, y: 0, scale: 1 },
    transition: { type: "spring" as const, stiffness: 140, damping: 20, delay: 0.08 * i },
  });

  return (
    <section className="overflow-hidden bg-black px-4 py-20 text-white sm:px-6 sm:py-24">
      <style>{`
        @keyframes neon-gradient-glow-card-with-flowing-border-flow {
          from { background-position: 50% 0%; }
          to { background-position: 50% 100%; }
        }
        @keyframes neon-gradient-glow-card-with-flowing-border-eq {
          0%, 100% { transform: scaleY(0.3); }
          50% { transform: scaleY(1); }
        }
        .neon-gradient-glow-card-with-flowing-border { padding: var(--neon-border); }
        .neon-gradient-glow-card-with-flowing-border::before,
        .neon-gradient-glow-card-with-flowing-border::after {
          content: "";
          position: absolute;
          inset: 0;
          z-index: -1;
          border-radius: inherit;
          background: linear-gradient(0deg, var(--neon-a), var(--neon-b)) 50% 0% / 100% 200%;
          animation: neon-gradient-glow-card-with-flowing-border-flow 3s ease-in-out infinite alternate;
        }
        .neon-gradient-glow-card-with-flowing-border::after { filter: blur(44px); opacity: 0.7; }
        .neon-gradient-glow-card-with-flowing-border-bar {
          transform-origin: bottom;
          animation: neon-gradient-glow-card-with-flowing-border-eq 1.1s ease-in-out infinite;
        }
        @media (prefers-reduced-motion: reduce) {
          .neon-gradient-glow-card-with-flowing-border::before,
          .neon-gradient-glow-card-with-flowing-border::after,
          .neon-gradient-glow-card-with-flowing-border-bar { animation: none; }
        }
      `}</style>

      <div className="mx-auto max-w-6xl">
        <div className="mx-auto max-w-2xl text-center">
          <p className="text-sm font-semibold uppercase tracking-widest text-fuchsia-300">Undertone</p>
          <h2 className="mt-3 text-4xl font-semibold tracking-tight sm:text-5xl">Your city, after dark</h2>
          <p className="mt-4 text-lg text-neutral-300">Tickets, mixes and membership in one place. Tonight is already filling up.</p>
        </div>

        <div className="mt-14 grid gap-8 md:grid-cols-3 md:gap-7">
          <motion.div {...enter(0)}>
            <NeonCard first="#ff2975" second="#00fff1" className="h-full">
              <div className="flex items-center justify-between text-xs font-medium uppercase tracking-widest text-neutral-400">
                <span>Fri 14 Nov</span>
                <span className="rounded-full bg-pink-500/15 px-2.5 py-1 text-pink-300">41 left</span>
              </div>
              <h3 className="mt-5 text-2xl font-semibold tracking-tight">Night Market Live</h3>
              <p className="mt-1 text-sm text-neutral-400">Pier 9 Warehouse, doors 19:30</p>
              <ul className="mt-6 space-y-3 border-t border-white/10 pt-5 text-sm">
                {lineup.map((l) => (
                  <li key={l.act} className="flex items-baseline gap-4">
                    <span className="font-mono text-neutral-400">{l.time}</span>
                    <span className="text-neutral-100">{l.act}</span>
                  </li>
                ))}
              </ul>
              <button type="button" className="mt-7 h-11 w-full rounded-xl bg-white text-sm font-semibold text-neutral-950 transition-colors hover:bg-pink-100 focus:outline-none focus-visible:ring-2 focus-visible:ring-pink-400 focus-visible:ring-offset-2 focus-visible:ring-offset-neutral-950">
                Get tickets from $28
              </button>
            </NeonCard>
          </motion.div>

          <motion.div {...enter(1)}>
            <NeonCard first="#a3ff12" second="#12c2ff" className="h-full">
              <p className="text-xs font-medium uppercase tracking-widest text-neutral-400">Now playing</p>
              <div className="mt-5 flex h-28 items-end gap-1.5" aria-hidden="true">
                {bars.map((b, i) => (
                  <span
                    key={i}
                    className="neon-gradient-glow-card-with-flowing-border-bar flex-1 rounded-sm bg-gradient-to-t from-sky-400 to-lime-300"
                    style={{ height: `${b * 100}%`, animationDelay: `${-i * 0.17}s`, animationDuration: `${0.9 + (i % 4) * 0.18}s` }}
                  />
                ))}
              </div>
              <h3 className="mt-6 text-2xl font-semibold tracking-tight">Glass Harbour</h3>
              <p className="mt-1 text-sm text-neutral-400">Mira Solveig, from the mix Slow Ferry</p>
              <div className="mt-6">
                <div className="h-1 overflow-hidden rounded-full bg-white/10">
                  <div className="h-full w-[38%] rounded-full bg-lime-300" />
                </div>
                <div className="mt-2 flex justify-between font-mono text-xs text-neutral-400">
                  <span>1:42</span>
                  <span>4:27</span>
                </div>
              </div>
              <button type="button" className="mt-5 h-11 w-full rounded-xl border border-white/20 text-sm font-semibold text-white transition-colors hover:bg-white/10 focus:outline-none focus-visible:ring-2 focus-visible:ring-lime-300">
                Open the full mix
              </button>
            </NeonCard>
          </motion.div>

          <motion.div {...enter(2)}>
            <NeonCard first="#ffb300" second="#9d4bff" className="h-full">
              <p className="text-xs font-medium uppercase tracking-widest text-neutral-400">Membership</p>
              <h3 className="mt-5 text-2xl font-semibold tracking-tight">Undertone Gold</h3>
              <p className="mt-3 flex items-baseline gap-1.5">
                <span className="text-5xl font-semibold tracking-tight">$9</span>
                <span className="text-sm text-neutral-400">a month, cancel any time</span>
              </p>
              <ul className="mt-6 space-y-3 border-t border-white/10 pt-5 text-sm text-neutral-200">
                {perks.map((p) => (
                  <li key={p} className="flex items-center gap-3">
                    <svg viewBox="0 0 20 20" className="h-4 w-4 flex-none text-amber-300" fill="none" stroke="currentColor" strokeWidth="2.4" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
                      <path d="M4 10.5l4 4 8-9" />
                    </svg>
                    {p}
                  </li>
                ))}
              </ul>
              <button type="button" className="mt-7 h-11 w-full rounded-xl bg-amber-300 text-sm font-semibold text-neutral-950 transition-colors hover:bg-amber-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-amber-300 focus-visible:ring-offset-2 focus-visible:ring-offset-neutral-950">
                Try a month free
              </button>
            </NeonCard>
          </motion.div>
        </div>
      </div>
    </section>
  );
}

Install with one command: npx shadcn@latest add https://we.inc/r/neon-gradient-glow-card-with-flowing-border.json

When to use it

How to customize it

Accessibility

Questions

How is the neon border made?

The outer box has a few pixels of padding and two pseudo-elements filled with a vertical gradient that is twice the height of the box. The dark inner panel covers the middle, so only the padding shows as a border. The second pseudo-element is the same gradient with a large blur, which becomes the glow.

What makes the colours move?

The gradient is taller than the card, and a keyframe animation slides its background position from top to bottom and back. The border and the glow use the same animation, so they always match.

Does the glow need the card size?

No. Both layers use `inset: 0` and inherit the corner radius, so they follow the card at any size and through any resize. No resize listener is involved.

Why is the glow cut off on my page?

A parent with `overflow: hidden` clips it. Give the cards a wrapper with enough padding, or move the clipping to an element further out, as the demo section does.

Can I use more than two colours?

Yes. Edit the `linear-gradient` in the style block and add stops, for example a third variable between the two existing ones.

Adapted from Neon Gradient Card in Magic UI, used under its open-source licence. Licences and credits

Related: Animated Gradient Border Card Wrapper, Card with Travelling Border Beam, Breathing Glow Border Status Panel.

More borders and glows or all free UI components. Start for free to have a full site built around it.

More in Borders

We.Inc is an AI-powered website builder you can resell under your own brand. Launch a branded client dashboard, bill on Stripe Connect, and deliver AI-generated websites in minutes. White-label plans start at $99 a month for 25 client sites, with a 7-day free trial and no per-site fees.

Product

Who It's For

Features

Resources

Company

View Sitemap