Soft, slowly drifting aurora light made of blurred token-colored glows with fine vertical curtains and an optional radial fade. Pure CSS layers, paused off-screen, still under reduced motion.
Quiet software for loud ideas.
Notes, tasks and docs that stay out of your way until you need them.
import { ArrowRight } from "lucide-react"
import { AuroraBackground } from "@/components/ballmac/aurora-background"
export default function AuroraBackgroundDemo() {
return (
<div className="relative isolate flex h-[380px] w-full max-w-2xl flex-col items-center justify-center overflow-hidden rounded-xl border bg-background px-6 text-center">
<AuroraBackground className="-z-10" />
<span className="inline-flex items-center gap-2 rounded-full border border-foreground/10 bg-background/50 px-3 py-1 text-xs font-medium backdrop-blur">
Introducing Acme 3
<ArrowRight className="size-3 text-muted-foreground rtl:rotate-180" aria-hidden="true" />
</span>
<h2 className="mt-5 max-w-lg text-4xl font-semibold tracking-tight text-balance sm:text-5xl">
Quiet software for loud ideas.
</h2>
<p className="mt-3 max-w-sm text-sm text-muted-foreground sm:text-base">
Notes, tasks and docs that stay out of your way until you need them.
</p>
<div className="mt-7 flex flex-wrap items-center justify-center gap-3">
<a
href="#"
className="inline-flex h-10 items-center rounded-full bg-primary px-5 text-sm font-medium text-primary-foreground outline-none transition-opacity duration-150 hover:opacity-90 focus-visible:ring-[3px] focus-visible:ring-ring/50"
>
Download for Mac
</a>
<a
href="#"
className="inline-flex h-10 items-center rounded-full border bg-background/60 px-5 text-sm font-medium backdrop-blur outline-none transition-colors duration-150 hover:bg-accent focus-visible:ring-[3px] focus-visible:ring-ring/50"
>
See what's new
</a>
</div>
</div>
)
}Installation
$ pnpm dlx shadcn@latest add @ballmac/aurora-backgroundCopy the source into your project.
components/ballmac/aurora-background.tsx// Ballmac UI: Aurora Background. https://ui.ballmac.com/components/aurora-background "use client" import * as React from "react" import { cn } from "@/lib/utils" type AuroraBackgroundProps = Omit<React.ComponentProps<"div">, "children"> & { /** Colors of the light, from left to right. Any CSS colors or var(). */ colors?: string[] /** Seconds for one slow drift of the light. Higher is calmer. */ duration?: number /** Overall strength of the light (0–1). */ intensity?: number /** Blur radius of the light in pixels. */ blur?: number /** Draw fine vertical curtains through the light, like a real aurora. */ curtains?: boolean /** Fade the aurora out toward the bottom and sides with an elliptical mask. */ radialMask?: boolean } // Where each light sits and how it drifts: [left %, top %, width %, drift x %, drift y %, scale]. const lights = [ [-10, -14, 62, 14, 8, 1.15], [20, -22, 58, -10, 12, 1.2], [46, -16, 60, 12, 6, 1.1], [68, -24, 52, -14, 10, 1.25], ] as const function AuroraBackground({ colors = ["var(--chart-1)", "var(--chart-2)", "var(--chart-4)", "var(--chart-1)"], duration = 22, intensity = 0.75, blur = 64, curtains = true, radialMask = true, className, style, ...props }: AuroraBackgroundProps) { const rootRef = React.useRef<HTMLDivElement>(null) React.useEffect(() => { const root = rootRef.current if (!root || typeof root.animate !== "function") return if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return const animations: Animation[] = [] root.querySelectorAll<HTMLElement>("[data-slot=aurora-light]").forEach((node, i) => { const [, , , dx, dy, scale] = lights[i % lights.length]! animations.push( node.animate( [ { transform: "translate3d(0, 0, 0) scale(1) rotate(0deg)" }, { transform: `translate3d(${dx}%, ${dy}%, 0) scale(${scale}) rotate(${i % 2 ? -8 : 8}deg)` }, ], { duration: duration * 1000 * (0.8 + i * 0.15), delay: -i * 3000, direction: "alternate", iterations: Infinity, easing: "cubic-bezier(0.45, 0, 0.55, 1)", } ) ) }) const rays = root.querySelector<HTMLElement>("[data-slot=aurora-curtains]") if (rays) { animations.push( rays.animate([{ backgroundPosition: "0% 0%" }, { backgroundPosition: "200% 0%" }], { duration: duration * 2500, iterations: Infinity, easing: "linear", }) ) } let visible = true const sync = () => { for (const a of animations) { if (visible && !document.hidden) a.play() else a.pause() } } const io = new IntersectionObserver(([entry]) => { visible = entry?.isIntersecting ?? true sync() }) io.observe(root) document.addEventListener("visibilitychange", sync) return () => { io.disconnect() document.removeEventListener("visibilitychange", sync) for (const a of animations) a.cancel() } }, [duration, colors.length, curtains]) const mask = radialMask ? "radial-gradient(ellipse 100% 80% at 50% 0%, black 35%, transparent 85%)" : undefined return ( <div ref={rootRef} aria-hidden="true" data-slot="aurora-background" className={cn("pointer-events-none absolute inset-0 overflow-hidden", className)} style={{ maskImage: mask, WebkitMaskImage: mask, ...style }} {...props} > <div className="absolute -inset-[10%] will-change-transform" style={{ filter: `blur(${blur}px) saturate(1.2)`, opacity: intensity }} > {colors.map((color, i) => { const [left, top, width] = lights[i % lights.length]! return ( <span key={i} data-slot="aurora-light" className="absolute block aspect-[1.6/1] rounded-full" style={{ left: `${left + (i >= lights.length ? 10 : 0)}%`, top: `${top}%`, width: `${width}%`, background: `radial-gradient(closest-side, ${color}, color-mix(in oklch, ${color} 40%, transparent) 55%, transparent)`, }} /> ) })} </div> {curtains && ( <div data-slot="aurora-curtains" className="absolute -inset-[5%] opacity-60 blur-[6px]" style={{ // Stripes of the page color cut soft vertical curtains through the light. backgroundImage: "repeating-linear-gradient(98deg, var(--background) 0%, var(--background) 4%, transparent 7%, transparent 9%, var(--background) 13%)", backgroundSize: "200% 100%", maskImage: "linear-gradient(to bottom, black, transparent 85%)", WebkitMaskImage: "linear-gradient(to bottom, black, transparent 85%)", }} /> )} </div> ) } export { AuroraBackground, type AuroraBackgroundProps }Update the import paths to match your project setup.
Usage
import { AuroraBackground } from "@/components/ballmac/aurora-background"The full example is in the Code tab above.
Examples
Warm, no curtains
Golden hour
Warm by default.
import { AuroraBackground } from "@/components/ballmac/aurora-background"
export default function AuroraBackgroundWarm() {
return (
<div className="relative isolate flex h-72 w-full max-w-xl flex-col items-center justify-center overflow-hidden rounded-xl border bg-background px-6 text-center">
<AuroraBackground
colors={["var(--chart-5)", "var(--chart-3)", "var(--chart-4)"]}
curtains={false}
intensity={0.5}
duration={16}
className="-z-10"
/>
<p className="font-mono text-xs tracking-widest text-muted-foreground uppercase">Golden hour</p>
<h3 className="mt-2 text-3xl font-semibold tracking-tight">Warm by default.</h3>
</div>
)
}API reference
| Prop | Type | Default |
|---|---|---|
colorsColors of the light, from left to right. Any CSS colors or var(). | string[] | ["var(--chart-1)", "var(--chart-2)", "var(--chart-4)", "var(--chart-1)"] |
durationSeconds for one slow drift of the light. Higher is calmer. | number | 22 |
intensityOverall strength of the light (0–1). | number | 0.75 |
blurBlur radius of the light in pixels. | number | 64 |
curtainsDraw fine vertical curtains through the light, like a real aurora. | boolean | true |
radialMaskFade the aurora out toward the bottom and sides with an elliptical mask. | boolean | true |
Also accepts the standard attributes of its root element.
Use with AI
Place <AuroraBackground /> as the first child of a relative, overflow-hidden hero; it is absolute inset-0, aria-hidden and pointer-events-none. The light sits along the top edge and fades down, so headline text reads on the page background. With the shadcn MCP server set up (guide), ask your agent:
Add the Ballmac UI Aurora Background (@ballmac/aurora-background) to this project with the shadcn MCP, then use it where it fits.
Use it for
- Behind a hero headline or a launch announcement
- Sign-in or waitlist pages that need atmosphere without imagery
Not for
- Behind dense content or data
- Several stacked sections (use once per page, at the top)
Registry JSON: https://ui.ballmac.com/r/aurora-background.json
Credits
Free to use in personal and commercial projects.
- npm
- None
- Registry
- shadcn/utils
Pairs well with
Text Reveal
Reveals a headline or paragraph word by word or character by character with a soft blur and rise, when it scrolls into view or on mount.
Shimmer Text
Text with a band of light sweeping across it, from muted to full foreground color. Suited to AI "thinking" and loading status lines; static under reduced motion.
Magnetic Button
The Ballmac Button with a magnetic pull: it drifts toward a nearby mouse pointer on a spring, and its label moves a little further for depth. Takes every Button prop.
Beams Background
Hairline rails with thin light beams rising along them and dissolving near the top, over a soft glow. Canvas, token colors, DPR aware, pauses off-screen, a still frame under reduced motion.