A card framed by a two-color neon border with a soft outer light and inner glow, tunable thickness, intensity and corner radius, with an optional warm-up flicker that never plays under reduced motion.
Most popular
$39per seat / month
- Unlimited projects
- Priority support
- Single sign-on
import { Check } from "lucide-react"
import { NeonCard } from "@/components/ballmac/neon-card"
export default function NeonCardDemo() {
return (
<NeonCard className="w-full max-w-xs" glow={0.7} radius="2xl" contentClassName="p-6">
<p className="font-mono text-xs tracking-wide text-muted-foreground uppercase">Most popular</p>
<p className="mt-2 flex items-baseline gap-1">
<span className="text-4xl font-semibold tracking-tight">$39</span>
<span className="text-sm text-muted-foreground">per seat / month</span>
</p>
<ul className="mt-4 grid gap-2 text-sm">
{["Unlimited projects", "Priority support", "Single sign-on"].map((x) => (
<li key={x} className="flex items-center gap-2">
<Check aria-hidden="true" className="size-4 text-foreground" />
{x}
</li>
))}
</ul>
</NeonCard>
)
}Installation
$ pnpm dlx shadcn@latest add @ballmac/neon-cardInstall the dependencies.
$ pnpm add motion@^12Copy the source into your project.
components/ballmac/neon-card.tsx// Ballmac UI: Neon Card. https://ui.ballmac.com/components/neon-card "use client" import * as React from "react" import { motion, useReducedMotion } from "motion/react" import { cn } from "@/lib/utils" type NeonTone = "chart-1" | "chart-2" | "chart-3" | "chart-4" | "chart-5" | "primary" | "destructive" const VAR: Record<NeonTone, string> = { "chart-1": "var(--chart-1)", "chart-2": "var(--chart-2)", "chart-3": "var(--chart-3)", "chart-4": "var(--chart-4)", "chart-5": "var(--chart-5)", primary: "var(--primary)", destructive: "var(--destructive)", } type NeonCardProps = Omit<React.ComponentProps<"div">, "ref"> & { /** Color at the start of the border. */ from?: NeonTone /** Color at the end of the border. */ to?: NeonTone /** Border thickness in pixels. */ borderWidth?: number /** How strongly the outer light spreads, from 0 to 1. */ glow?: number /** Let the light flicker like a neon sign that is warming up. Off by default and never on under reduced motion. */ flicker?: boolean /** Corner radius. */ radius?: "lg" | "xl" | "2xl" | "3xl" /** Classes for the inner surface. */ contentClassName?: string } const RADIUS = { lg: ["rounded-lg", "rounded-[calc(var(--radius-lg)-var(--nw))]"], xl: ["rounded-xl", "rounded-[calc(var(--radius-xl)-var(--nw))]"], "2xl": ["rounded-2xl", "rounded-[calc(var(--radius-2xl)-var(--nw))]"], "3xl": ["rounded-3xl", "rounded-[calc(var(--radius-3xl)-var(--nw))]"], } function NeonCard({ from = "chart-1", to = "chart-4", borderWidth = 1.5, glow = 0.6, flicker = false, radius = "xl", className, contentClassName, children, style, ...props }: NeonCardProps) { const reduce = useReducedMotion() const gradient = `linear-gradient(135deg, ${VAR[from]}, ${VAR[to]})` const [outer, inner] = RADIUS[radius] const flickers = flicker && !reduce return ( <div data-slot="neon-card" className={cn("group/neon relative isolate", outer, className)} style={{ ...style, ["--nw" as string]: `${borderWidth}px` } as React.CSSProperties} {...props} > {/* The light: the same gradient, blurred, behind the card. */} <motion.span aria-hidden="true" className={cn("pointer-events-none absolute -inset-1 -z-10 blur-xl", outer)} style={{ background: gradient, opacity: 0.18 + glow * 0.5 }} animate={flickers ? { opacity: [0.18 + glow * 0.5, 0.1, 0.18 + glow * 0.5, 0.05, 0.18 + glow * 0.5, 0.18 + glow * 0.5] } : undefined} transition={flickers ? { duration: 4, repeat: Infinity, times: [0, 0.04, 0.08, 0.12, 0.16, 1], ease: "linear" } : undefined} /> <div className={cn("p-[var(--nw)]", outer)} style={{ background: gradient }}> <div className={cn("relative h-full bg-card text-card-foreground", inner, contentClassName)}> <span aria-hidden="true" className={cn("pointer-events-none absolute inset-0 opacity-60 dark:opacity-100", inner)} style={{ boxShadow: `inset 0 0 28px -6px color-mix(in oklab, ${VAR[from]} ${Math.round(glow * 45)}%, transparent)` }} /> <div className="relative">{children}</div> </div> </div> </div> ) } export { NeonCard, type NeonCardProps, type NeonTone }Update the import paths to match your project setup.
Usage
import { NeonCard } from "@/components/ballmac/neon-card"The full example is in the Code tab above.
Examples
Tones and flicker
chart-2 → chart-1
Steady
chart-3 → destructive
Steady
chart-5 → chart-4
Flicker
import { NeonCard, type NeonTone } from "@/components/ballmac/neon-card"
const sets: { from: NeonTone; to: NeonTone; flicker?: boolean }[] = [
{ from: "chart-2", to: "chart-1" },
{ from: "chart-3", to: "destructive" },
{ from: "chart-5", to: "chart-4", flicker: true },
]
export default function NeonCardTones() {
return (
<div className="grid w-full max-w-md gap-4 sm:grid-cols-3">
{sets.map((s) => (
<NeonCard key={s.from + s.to} from={s.from} to={s.to} flicker={s.flicker} radius="xl" contentClassName="p-4 text-center">
<p className="font-mono text-[11px] text-muted-foreground">
{s.from} → {s.to}
</p>
<p className="mt-1 text-sm font-medium">{s.flicker ? "Flicker" : "Steady"}</p>
</NeonCard>
))}
</div>
)
}API reference
| Prop | Type | Default |
|---|---|---|
fromColor at the start of the border. | NeonTone | "chart-1" |
toColor at the end of the border. | NeonTone | "chart-4" |
borderWidthBorder thickness in pixels. | number | 1.5 |
glowHow strongly the outer light spreads, from 0 to 1. | number | 0.6 |
flickerLet the light flicker like a neon sign that is warming up. Off by default and never on under reduced motion. | boolean | false |
radiusCorner radius. | "lg" | "xl" | "2xl" | "3xl" | "xl" |
contentClassNameClasses for the inner surface. | string | — |
Also accepts the standard attributes of its root element.
Accessibility
| Key | Action |
|---|---|
| Contrast | Content sits on the normal card surface; the glow is decorative |
| Reduced motion | Flicker is never played |
Use with AI
<NeonCard from to borderWidth glow flicker radius contentClassName>content</NeonCard>. Colors are theme tokens, so it follows any theme. The light is strongest in dark mode. With the shadcn MCP server set up (guide), ask your agent:
Add the Ballmac UI Neon Card (@ballmac/neon-card) to this project with the shadcn MCP, then use it where it fits.
Use it for
- A single highlighted plan, product or announcement
- Dark-themed landing pages
Not for
- Several cards at once
- A rotating border (glow-border)
Registry JSON: https://ui.ballmac.com/r/neon-card.json
Credits
Free to use in personal and commercial projects.
- npm
- motion@^12
- Registry
- shadcn/utils
Pairs well with
Glow Border
Wraps any content in a border drawn by a rotating conic gradient in theme colors, with a soft outer glow. Speed, width, radius and arc length are props; it stops at a still angle under reduced motion.
Magic Card
A card with a two-color light that follows the pointer along its border, with an optional soft glow inside, driven by CSS variables so moving never re-renders, and a steady light on keyboard focus.
Card
A composable content surface with compact spacing, an action slot, and optional interactive feedback.
Animated Beam
An SVG beam that connects two elements with a curved path and sends a glowing gradient pulse along it. Follows layout changes, pauses off-screen, static under reduced motion.