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.
// 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
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.
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.
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.
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.
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.
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.