Mesh Gradient Background
Original · freeanimated mesh 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-mesh-gradient.json"use client";
import { useId, useRef, useState, type KeyboardEvent } from "react";
import { motion, useReducedMotion } from "motion/react";
type PaletteId = "aurora" | "ember" | "meadow";
interface Palette {
id: PaletteId;
label: string;
hint: string;
colors: [string, string, string, string, string, string];
}
interface Slot {
top: string;
left: string;
size: string;
dx: number;
dy: number;
scale: number;
dur: number;
}
const PALETTES: Palette[] = [
{
id: "aurora",
label: "Aurora",
hint: "indigo · violet · sky",
colors: [
"bg-indigo-500",
"bg-violet-500",
"bg-sky-400",
"bg-emerald-400",
"bg-indigo-400",
"bg-violet-600",
],
},
{
id: "ember",
label: "Ember",
hint: "rose · amber · violet",
colors: [
"bg-rose-500",
"bg-amber-400",
"bg-rose-400",
"bg-amber-500",
"bg-violet-500",
"bg-rose-600",
],
},
{
id: "meadow",
label: "Meadow",
hint: "emerald · sky · indigo",
colors: [
"bg-emerald-500",
"bg-sky-400",
"bg-emerald-400",
"bg-sky-500",
"bg-indigo-400",
"bg-emerald-300",
],
},
];
const SLOTS: Slot[] = [
{ top: "-12%", left: "-8%", size: "58%", dx: 70, dy: 52, scale: 1.16, dur: 18 },
{ top: "6%", left: "56%", size: "52%", dx: -82, dy: 60, scale: 1.1, dur: 23 },
{ top: "52%", left: "4%", size: "48%", dx: 92, dy: -48, scale: 1.2, dur: 20 },
{ top: "-6%", left: "38%", size: "46%", dx: -58, dy: 82, scale: 1.12, dur: 25 },
{ top: "56%", left: "60%", size: "50%", dx: -74, dy: -62, scale: 1.18, dur: 19 },
{ top: "26%", left: "26%", size: "40%", dx: 52, dy: 44, scale: 1.08, dur: 27 },
];
const NOISE =
"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)'/%3E%3C/svg%3E\")";
export default function BgxMeshGradient() {
const prefersReduced = useReducedMotion();
const uid = useId();
const softId = `${uid}-soft`;
const speedId = `${uid}-speed`;
const [paletteId, setPaletteId] = useState<PaletteId>("aurora");
const [blur, setBlur] = useState(58);
const [speed, setSpeed] = useState(1);
const [playing, setPlaying] = useState(true);
const [grain, setGrain] = useState(true);
const paletteRefs = useRef<Array<HTMLButtonElement | null>>([]);
const palette = PALETTES.find((p) => p.id === paletteId) ?? PALETTES[0];
const animating = playing && !prefersReduced;
const grainOn = grain;
function onPaletteKey(e: KeyboardEvent<HTMLDivElement>) {
const idx = PALETTES.findIndex((p) => p.id === paletteId);
let next = idx;
if (e.key === "ArrowRight" || e.key === "ArrowDown") {
next = (idx + 1) % PALETTES.length;
} else if (e.key === "ArrowLeft" || e.key === "ArrowUp") {
next = (idx - 1 + PALETTES.length) % PALETTES.length;
} else if (e.key === "Home") {
next = 0;
} else if (e.key === "End") {
next = PALETTES.length - 1;
} else {
return;
}
e.preventDefault();
setPaletteId(PALETTES[next].id);
paletteRefs.current[next]?.focus();
}
return (
<section className="relative isolate w-full overflow-hidden bg-slate-50 px-6 py-16 text-slate-900 dark:bg-slate-950 dark:text-slate-100 sm:px-8 sm:py-24 min-h-[44rem]">
<style>{`
@keyframes bgxmesh-grain {
0%, 100% { transform: translate3d(0, 0, 0); }
25% { transform: translate3d(-3%, 2%, 0); }
50% { transform: translate3d(2%, -2%, 0); }
75% { transform: translate3d(-1%, 3%, 0); }
}
.bgxmesh-grain-anim { animation: bgxmesh-grain 7.5s steps(5) infinite; }
@media (prefers-reduced-motion: reduce) {
.bgxmesh-grain-anim { animation: none !important; }
}
`}</style>
{/* Animated mesh gradient field */}
<div aria-hidden className="pointer-events-none absolute inset-0 z-0 overflow-hidden">
{SLOTS.map((slot, i) => (
<motion.div
key={i}
className={`absolute aspect-square rounded-full opacity-70 mix-blend-multiply will-change-transform dark:opacity-60 dark:mix-blend-screen ${palette.colors[i]}`}
style={{ top: slot.top, left: slot.left, width: slot.size, filter: `blur(${blur}px)` }}
animate={
animating
? {
x: [0, slot.dx, -slot.dx * 0.5, 0],
y: [0, slot.dy, slot.dy * 0.4, 0],
scale: [1, slot.scale, 1],
}
: { x: 0, y: 0, scale: 1 }
}
transition={
animating
? { duration: slot.dur / speed, repeat: Infinity, ease: "easeInOut" }
: { duration: 0.6, ease: "easeOut" }
}
/>
))}
</div>
{/* Film grain */}
{grainOn ? (
<div
aria-hidden
className={`pointer-events-none absolute inset-0 z-0 opacity-40 mix-blend-overlay dark:opacity-30 dark:mix-blend-soft-light ${
animating ? "bgxmesh-grain-anim" : ""
}`}
style={{ backgroundImage: NOISE, backgroundSize: "160px 160px" }}
/>
) : null}
{/* Readability scrim (keeps copy legible over vivid fields) */}
<div
aria-hidden
className="pointer-events-none absolute inset-0 z-0 bg-gradient-to-r from-slate-50 via-slate-50/50 to-transparent dark:from-slate-950 dark:via-slate-950/45"
/>
{/* Edge vignette */}
<div
aria-hidden
className="pointer-events-none absolute inset-0 z-0 block dark:hidden"
style={{
background:
"radial-gradient(135% 120% at 50% -10%, rgba(248,250,252,0) 45%, rgba(248,250,252,0.9) 100%)",
}}
/>
<div
aria-hidden
className="pointer-events-none absolute inset-0 z-0 hidden dark:block"
style={{
background:
"radial-gradient(135% 120% at 50% -10%, rgba(2,6,23,0) 42%, rgba(2,6,23,0.92) 100%)",
}}
/>
{/* Content */}
<div className="relative z-10 mx-auto grid max-w-6xl items-center gap-10 lg:grid-cols-2 lg:gap-16">
<div>
<span className="inline-flex items-center gap-2 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 dark:border-white/10 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 2.5l1.9 5.1 5.1 1.9-5.1 1.9L12 16.5l-1.9-5.1L5 9.5l5.1-1.9L12 2.5z"
fill="currentColor"
/>
</svg>
Backgrounds · Mesh gradient
</span>
<h2 className="mt-5 text-balance text-4xl font-semibold leading-[1.05] tracking-tight sm:text-5xl">
A backdrop that moves like light through frosted glass.
</h2>
<p className="mt-5 max-w-lg text-pretty text-base leading-relaxed text-slate-600 dark:text-slate-300 sm:text-lg">
Six blurred color fields drift on independent timers to build a living mesh gradient.
Retune the palette, soften the edges, or slow it right down. No canvas, no WebGL — just
CSS blur and transforms, and it holds up in light and dark.
</p>
<dl className="mt-8 flex flex-wrap gap-x-8 gap-y-4">
<div>
<dt className="text-xs uppercase tracking-wide text-slate-500 dark:text-slate-400">
Render cost
</dt>
<dd className="mt-1 text-sm font-semibold text-slate-900 dark:text-slate-100">
6 layers · GPU compositing
</dd>
</div>
<div>
<dt className="text-xs uppercase tracking-wide text-slate-500 dark:text-slate-400">
Motion
</dt>
<dd className="mt-1 text-sm font-semibold text-slate-900 dark:text-slate-100">
Honors reduced-motion
</dd>
</div>
</dl>
</div>
{/* Control panel */}
<div className="rounded-2xl border border-slate-200/80 bg-white/70 p-6 shadow-xl shadow-slate-900/5 backdrop-blur-md dark:border-white/10 dark:bg-slate-900/50 dark:shadow-black/30 sm:p-7">
<div className="flex items-center justify-between gap-4">
<div>
<h3 className="text-sm font-semibold text-slate-900 dark:text-slate-100">
Composition
</h3>
<p className="mt-0.5 text-xs text-slate-500 dark:text-slate-400">
Live controls for the field behind you
</p>
</div>
<button
type="button"
aria-pressed={animating}
disabled={!!prefersReduced}
onClick={() => setPlaying((v) => !v)}
className="inline-flex items-center gap-2 rounded-lg border border-slate-300 bg-white px-3 py-2 text-sm font-medium text-slate-700 shadow-sm transition-colors hover:bg-slate-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white disabled:cursor-not-allowed disabled:opacity-60 dark:border-white/15 dark:bg-white/5 dark:text-slate-200 dark:hover:bg-white/10 dark:focus-visible:ring-offset-slate-900"
>
{animating ? (
<svg viewBox="0 0 24 24" className="h-4 w-4" fill="currentColor" aria-hidden>
<rect x="6" y="5" width="4" height="14" rx="1" />
<rect x="14" y="5" width="4" height="14" rx="1" />
</svg>
) : (
<svg viewBox="0 0 24 24" className="h-4 w-4" fill="currentColor" aria-hidden>
<path d="M8 5.5v13a1 1 0 0 0 1.5.87l10-6.5a1 1 0 0 0 0-1.74l-10-6.5A1 1 0 0 0 8 5.5z" />
</svg>
)}
<span>
{prefersReduced ? "Motion reduced" : animating ? "Pause" : "Play"}
</span>
</button>
</div>
{/* Palette radiogroup */}
<div className="mt-6">
<span className="text-xs font-medium uppercase tracking-wide text-slate-500 dark:text-slate-400">
Palette
</span>
<div
role="radiogroup"
aria-label="Color palette"
onKeyDown={onPaletteKey}
className="mt-2 grid grid-cols-3 gap-2"
>
{PALETTES.map((p, i) => {
const selected = p.id === paletteId;
return (
<button
key={p.id}
ref={(el) => {
paletteRefs.current[i] = el;
}}
type="button"
role="radio"
aria-checked={selected}
tabIndex={selected ? 0 : -1}
onClick={() => setPaletteId(p.id)}
className={`group rounded-xl border px-3 py-2.5 text-left transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:focus-visible:ring-offset-slate-900 ${
selected
? "border-indigo-500 bg-indigo-50 dark:border-indigo-400 dark:bg-indigo-500/10"
: "border-slate-200 bg-white/40 hover:border-slate-300 dark:border-white/10 dark:bg-white/5 dark:hover:border-white/20"
}`}
>
<span className="flex items-center gap-1">
{p.colors.slice(0, 4).map((c, j) => (
<span
key={j}
className={`h-3 w-3 rounded-full ring-1 ring-black/10 dark:ring-white/10 ${c}`}
/>
))}
</span>
<span className="mt-2 block text-sm font-semibold text-slate-900 dark:text-slate-100">
{p.label}
</span>
<span className="block text-[11px] text-slate-500 dark:text-slate-400">
{p.hint}
</span>
</button>
);
})}
</div>
</div>
{/* Softness slider */}
<div className="mt-6">
<div className="flex items-center justify-between">
<label htmlFor={softId} className="text-xs font-medium uppercase tracking-wide text-slate-500 dark:text-slate-400">
Softness
</label>
<span className="tabular-nums text-xs font-semibold text-slate-700 dark:text-slate-300">
{blur}px blur
</span>
</div>
<input
id={softId}
type="range"
min={20}
max={100}
step={1}
value={blur}
onChange={(e) => setBlur(Number(e.target.value))}
className="mt-2 w-full cursor-pointer accent-indigo-500 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:accent-indigo-400 dark:focus-visible:ring-offset-slate-900"
/>
</div>
{/* Speed slider */}
<div className="mt-5">
<div className="flex items-center justify-between">
<label htmlFor={speedId} className="text-xs font-medium uppercase tracking-wide text-slate-500 dark:text-slate-400">
Drift speed
</label>
<span className="tabular-nums text-xs font-semibold text-slate-700 dark:text-slate-300">
{speed.toFixed(2)}×
</span>
</div>
<input
id={speedId}
type="range"
min={0.25}
max={2}
step={0.25}
value={speed}
disabled={!!prefersReduced}
onChange={(e) => setSpeed(Number(e.target.value))}
className="mt-2 w-full cursor-pointer accent-indigo-500 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white disabled:cursor-not-allowed disabled:opacity-50 dark:accent-indigo-400 dark:focus-visible:ring-offset-slate-900"
/>
</div>
{/* Grain switch */}
<div className="mt-6 flex items-center justify-between border-t border-slate-200/70 pt-5 dark:border-white/10">
<span className="text-sm font-medium text-slate-700 dark:text-slate-200">
Film grain
</span>
<button
type="button"
role="switch"
aria-checked={grain}
aria-label="Film grain overlay"
onClick={() => setGrain((v) => !v)}
className={`relative inline-flex h-6 w-11 shrink-0 items-center rounded-full transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:focus-visible:ring-offset-slate-900 ${
grain ? "bg-indigo-500" : "bg-slate-300 dark:bg-slate-700"
}`}
>
<span
className={`inline-block h-5 w-5 transform rounded-full bg-white shadow transition-transform ${
grain ? "translate-x-5" : "translate-x-0.5"
}`}
/>
</button>
</div>
{prefersReduced ? (
<p className="mt-4 text-xs leading-relaxed text-slate-500 dark:text-slate-400">
Your system requests reduced motion, so the field is held still and drift controls are
off. Palette and softness still apply.
</p>
) : null}
</div>
</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.

Aurora Background
Originalaurora gradient background

Grid Background
Originalgrid line background with fade

Dots Background
Originaldotted grid background

Noise Background
Originalnoise/grain gradient background

