Aurora Background
Original · freeaurora gradient background
Copy prompt gives Claude Code, Cursor or v0 a ready-to-paste prompt that recreates this exact component.
npx shadcn@latest add https://webinnoventix.com/r/bgx-aurora.json"use client";
import { useId, useRef, useState } from "react";
import type { KeyboardEvent } from "react";
import { motion, useReducedMotion, type Variants } from "motion/react";
type PaletteKey = "boreal" | "nebula" | "solar";
interface Palette {
key: PaletteKey;
label: string;
hint: string;
accent: string;
ring: string;
dot: string;
stops: [string, string, string, string];
}
const PALETTES: Palette[] = [
{
key: "boreal",
label: "Boreal",
hint: "Green over glacier blue",
accent: "#34d399",
ring: "focus-visible:ring-emerald-400/70",
dot: "bg-emerald-400",
stops: [
"rgba(16,185,129,0.60)",
"rgba(56,189,248,0.52)",
"rgba(45,212,191,0.50)",
"rgba(110,231,183,0.55)",
],
},
{
key: "nebula",
label: "Nebula",
hint: "Violet, indigo, a rose flare",
accent: "#a78bfa",
ring: "focus-visible:ring-violet-400/70",
dot: "bg-violet-400",
stops: [
"rgba(139,92,246,0.58)",
"rgba(99,102,241,0.52)",
"rgba(244,63,94,0.44)",
"rgba(56,189,248,0.46)",
],
},
{
key: "solar",
label: "Solar",
hint: "Amber dawn into rose",
accent: "#fbbf24",
ring: "focus-visible:ring-amber-400/70",
dot: "bg-amber-400",
stops: [
"rgba(251,191,36,0.56)",
"rgba(244,63,94,0.46)",
"rgba(251,146,60,0.50)",
"rgba(16,185,129,0.38)",
],
},
];
const GRAIN_URL =
"url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.82' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.55'/%3E%3C/svg%3E\")";
const CSS = `
@keyframes bgxaur-drift-a {
0%,100% { transform: translate3d(-6%, -3%, 0) scale(1.12) rotate(0deg); }
50% { transform: translate3d(9%, 7%, 0) scale(1.3) rotate(9deg); }
}
@keyframes bgxaur-drift-b {
0%,100% { transform: translate3d(6%, -6%, 0) scale(1.22) rotate(0deg); }
50% { transform: translate3d(-8%, 4%, 0) scale(1.08) rotate(-11deg); }
}
@keyframes bgxaur-drift-c {
0%,100% { transform: translate3d(-4%, 8%, 0) scale(1.05) rotate(0deg); }
50% { transform: translate3d(7%, -5%, 0) scale(1.28) rotate(7deg); }
}
@keyframes bgxaur-drift-d {
0%,100% { transform: translate3d(5%, 5%, 0) scale(1.0) rotate(0deg); opacity: 0.85; }
50% { transform: translate3d(-6%, -7%, 0) scale(1.25) rotate(-6deg); opacity: 1; }
}
@keyframes bgxaur-grain-shift {
0% { background-position: 0 0; }
100% { background-position: 140px 90px; }
}
.bgxaur-layer-1 { animation: bgxaur-drift-a 23s ease-in-out infinite; }
.bgxaur-layer-2 { animation: bgxaur-drift-b 29s ease-in-out infinite; }
.bgxaur-layer-3 { animation: bgxaur-drift-c 26s ease-in-out infinite; }
.bgxaur-layer-4 { animation: bgxaur-drift-d 19s ease-in-out infinite; }
.bgxaur-grain { animation: bgxaur-grain-shift 6s steps(5) infinite; }
@media (prefers-reduced-motion: reduce) {
.bgxaur-layer-1, .bgxaur-layer-2, .bgxaur-layer-3, .bgxaur-layer-4, .bgxaur-grain {
animation: none !important;
}
}
`;
const container: Variants = {
hidden: {},
show: { transition: { staggerChildren: 0.08, delayChildren: 0.08 } },
};
const item: Variants = {
hidden: { opacity: 0, y: 18 },
show: { opacity: 1, y: 0, transition: { duration: 0.7, ease: "easeOut" } },
};
export default function BgxAurora() {
const reduce = useReducedMotion();
const uid = useId();
const intensityId = `${uid}-intensity`;
const [paletteKey, setPaletteKey] = useState<PaletteKey>("boreal");
const [intensity, setIntensity] = useState<number>(64);
const [playing, setPlaying] = useState<boolean>(true);
const radioRefs = useRef<Array<HTMLButtonElement | null>>([]);
const palette = PALETTES.find((p) => p.key === paletteKey) ?? PALETTES[0];
const strength = Math.min(1, 0.15 + (intensity / 100) * 0.9);
const playState = playing ? "running" : "paused";
const onRadioKeyNav = (e: KeyboardEvent<HTMLButtonElement>, idx: number) => {
const last = PALETTES.length - 1;
let next = idx;
switch (e.key) {
case "ArrowRight":
case "ArrowDown":
next = idx === last ? 0 : idx + 1;
break;
case "ArrowLeft":
case "ArrowUp":
next = idx === 0 ? last : idx - 1;
break;
case "Home":
next = 0;
break;
case "End":
next = last;
break;
default:
return;
}
e.preventDefault();
setPaletteKey(PALETTES[next].key);
radioRefs.current[next]?.focus();
};
const layerDefs = [
{ cls: "bgxaur-layer-1", top: "-18%", left: "-12%", size: "72%", stop: palette.stops[0] },
{ cls: "bgxaur-layer-2", top: "-8%", left: "34%", size: "68%", stop: palette.stops[1] },
{ cls: "bgxaur-layer-3", top: "18%", left: "-6%", size: "62%", stop: palette.stops[2] },
{ cls: "bgxaur-layer-4", top: "6%", left: "44%", size: "54%", stop: palette.stops[3] },
];
return (
<section className="relative w-full overflow-hidden bg-slate-50 text-slate-900 dark:bg-slate-950 dark:text-slate-100">
<style>{CSS}</style>
{/* ---------- Aurora field (decorative) ---------- */}
<div aria-hidden className="pointer-events-none absolute inset-0">
{/* faint base wash so low intensity still has presence */}
<div
className="absolute inset-0"
style={{
background: `radial-gradient(120% 90% at 50% -10%, ${palette.accent}22, transparent 60%)`,
}}
/>
{/* blurred, group-blended aurora stack */}
<div
className="absolute inset-0 mix-blend-multiply dark:mix-blend-screen"
style={{ opacity: strength, filter: "blur(64px) saturate(155%)" }}
>
{layerDefs.map((l) => (
<div
key={l.cls}
className={`${l.cls} absolute rounded-full`}
style={{
top: l.top,
left: l.left,
width: l.size,
height: l.size,
background: `radial-gradient(closest-side, ${l.stop}, transparent 78%)`,
willChange: "transform",
animationPlayState: playState,
}}
/>
))}
</div>
{/* film grain */}
<div
className="bgxaur-grain absolute inset-0 opacity-[0.06] mix-blend-soft-light dark:opacity-[0.1] dark:mix-blend-overlay"
style={{
backgroundImage: GRAIN_URL,
backgroundSize: "140px 140px",
animationPlayState: playState,
}}
/>
{/* edge vignette / bottom fade for depth */}
<div className="absolute inset-0 bg-gradient-to-b from-slate-50/60 via-transparent to-slate-50 dark:from-slate-950/60 dark:to-slate-950" />
<div
className="absolute inset-0"
style={{
background:
"radial-gradient(115% 80% at 50% 8%, transparent 45%, rgba(2,6,23,0.10) 100%)",
}}
/>
</div>
{/* ---------- Content ---------- */}
<motion.div
variants={container}
initial={reduce ? false : "hidden"}
animate="show"
className="relative mx-auto flex min-h-[620px] max-w-5xl flex-col justify-center px-6 py-24 sm:px-8 sm:py-32"
>
<motion.p
variants={item}
className="mb-6 inline-flex items-center gap-2 self-start rounded-full border border-slate-300/70 bg-white/60 px-3 py-1 text-xs font-medium tracking-wide text-slate-600 backdrop-blur-md dark:border-white/15 dark:bg-white/5 dark:text-slate-300"
>
<svg viewBox="0 0 24 24" className="h-3.5 w-3.5" fill="none" aria-hidden>
<path
d="M12 2c.5 3.4 2.1 5 5.5 5.5C14.1 8 12.5 9.6 12 13c-.5-3.4-2.1-5-5.5-5.5C9.9 7 11.5 5.4 12 2Z"
fill="currentColor"
/>
<path
d="M19 13c.3 1.9 1.1 2.7 3 3-1.9.3-2.7 1.1-3 3-.3-1.9-1.1-2.7-3-3 1.9-.3 2.7-1.1 3-3Z"
fill="currentColor"
opacity="0.7"
/>
</svg>
FreeCode / Backgrounds
</motion.p>
<motion.h1
variants={item}
className="max-w-3xl text-balance text-5xl font-semibold leading-[1.02] tracking-tight sm:text-6xl md:text-7xl"
>
Aurora, painted live in the browser.
</motion.h1>
<motion.p
variants={item}
className="mt-6 max-w-xl text-pretty text-lg leading-relaxed text-slate-600 dark:text-slate-300"
>
A drifting gradient field built from four layers of radial light — no
canvas, no WebGL, no images. Pick a palette, dial the intensity, and
drop it behind any hero, pricing table, or sign-up card.
</motion.p>
{/* feature chips */}
<motion.ul
variants={item}
className="mt-6 flex flex-wrap gap-2 text-xs font-medium text-slate-500 dark:text-slate-400"
>
{["No <canvas>", "No WebGL", "GPU transforms only", "Light + dark"].map(
(chip) => (
<li
key={chip}
className="rounded-full border border-slate-200 bg-white/50 px-2.5 py-1 backdrop-blur-sm dark:border-white/10 dark:bg-white/5"
>
{chip}
</li>
)
)}
</motion.ul>
{/* ---------- Controls ---------- */}
<motion.div
variants={item}
role="group"
aria-label="Aurora controls"
className="mt-10 w-full max-w-xl rounded-3xl border border-slate-200/80 bg-white/65 p-5 shadow-xl shadow-slate-900/5 backdrop-blur-xl dark:border-white/10 dark:bg-white/[0.06] dark:shadow-black/30 sm:p-6"
>
{/* palette radiogroup */}
<div className="flex flex-col gap-3">
<span className="text-sm font-semibold text-slate-800 dark:text-slate-100">
Palette
</span>
<div
role="radiogroup"
aria-label="Aurora palette"
className="inline-flex flex-wrap gap-1.5 rounded-2xl bg-slate-100/80 p-1.5 dark:bg-white/5"
>
{PALETTES.map((p, i) => {
const selected = p.key === paletteKey;
return (
<button
key={p.key}
type="button"
role="radio"
aria-checked={selected}
tabIndex={selected ? 0 : -1}
ref={(el) => {
radioRefs.current[i] = el;
}}
onClick={() => setPaletteKey(p.key)}
onKeyDown={(e) => onRadioKeyNav(e, i)}
title={p.hint}
className={[
"flex items-center gap-2 rounded-xl px-3.5 py-2 text-sm font-medium outline-none transition",
"focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-offset-transparent",
palette.ring,
selected
? "bg-white text-slate-900 shadow-sm dark:bg-white/15 dark:text-white"
: "text-slate-500 hover:text-slate-800 dark:text-slate-400 dark:hover:text-slate-200",
].join(" ")}
>
<span
className={`h-2.5 w-2.5 rounded-full ${p.dot} ${
selected ? "" : "opacity-60"
}`}
/>
{p.label}
</button>
);
})}
</div>
<p className="text-xs text-slate-500 dark:text-slate-400">
{palette.hint}
</p>
</div>
{/* intensity slider */}
<div className="mt-6 flex flex-col gap-2">
<div className="flex items-center justify-between">
<label
htmlFor={intensityId}
className="text-sm font-semibold text-slate-800 dark:text-slate-100"
>
Intensity
</label>
<span className="tabular-nums text-sm font-medium text-slate-500 dark:text-slate-400">
{intensity}%
</span>
</div>
<input
id={intensityId}
type="range"
min={0}
max={100}
step={1}
value={intensity}
onChange={(e) => setIntensity(Number(e.currentTarget.value))}
aria-valuetext={`${intensity} percent`}
style={{ accentColor: palette.accent }}
className={`h-2 w-full cursor-pointer appearance-none rounded-full bg-slate-200 outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-offset-transparent dark:bg-white/10 ${palette.ring}`}
/>
</div>
{/* motion switch */}
<div className="mt-6 flex items-center justify-between">
<div className="flex flex-col">
<span className="text-sm font-semibold text-slate-800 dark:text-slate-100">
Motion
</span>
<span className="text-xs text-slate-500 dark:text-slate-400">
{reduce
? "Paused — respecting your reduced-motion setting"
: playing
? "Layers are drifting"
: "Layers are frozen"}
</span>
</div>
<button
type="button"
role="switch"
aria-checked={playing}
aria-label="Toggle aurora motion"
onClick={() => setPlaying((v) => !v)}
className={`relative inline-flex h-7 w-12 shrink-0 items-center rounded-full outline-none transition-colors focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-offset-transparent ${palette.ring} ${
playing ? "" : "bg-slate-300 dark:bg-white/15"
}`}
style={playing ? { backgroundColor: palette.accent } : undefined}
>
<span
className={`inline-block h-5 w-5 transform rounded-full bg-white shadow-sm transition-transform ${
playing ? "translate-x-6" : "translate-x-1"
}`}
/>
</button>
</div>
{/* live status readout */}
<p
aria-live="polite"
className="mt-5 border-t border-slate-200/70 pt-4 text-xs font-medium tabular-nums text-slate-500 dark:border-white/10 dark:text-slate-400"
>
{palette.label} · Intensity {intensity}% · Motion{" "}
{playing && !reduce ? "on" : "off"}
</p>
</motion.div>
</motion.div>
</section>
);
}Dependencies
Licence
Built by Web Innoventix. Free for personal and commercial use, no attribution required.
Built by Web Innoventix
Need a custom interface, a full website, or SEO that gets you cited by AI? We design, build and rank it end to end.
Get a free quoteSimilar components
Browse all →
Aurora Blobs Background
OriginalAn animated hero section with soft, ever-drifting aurora gradient blobs floating behind staggered, blur-in content.

Retro Perspective Grid
OriginalA launch and waitlist section set over an infinite neon grid that scrolls into a glowing horizon with rising particles.

Conic Gradient Flow
OriginalA frosted hero backed by twin counter-rotating conic gradients, with flowing gradient text and an animated gradient-border stat card.

Spotlight Follow Background
OriginalA dot-grid section where a spring-eased spotlight follows the cursor and reveals a hidden lattice of coloured dots.

Dot Pattern Background
pattern.tsxA reusable animated background for React and Tailwind: dot pattern background.

Flickering Grid Background
grid.tsxA reusable animated background for React and Tailwind: flickering grid background.

Grid Pattern Background
pattern.tsxA reusable animated background for React and Tailwind: grid pattern background.

Retro Grid Background
grid.jsonA reusable animated background for React and Tailwind: retro grid background.

Mesh Gradient Background
Originalanimated mesh gradient background

Grid Background
Originalgrid line background with fade

Dots Background
Originaldotted grid background

Noise Background
Originalnoise/grain gradient background

