Noise Background
Original · freenoise/grain 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-noise.json"use client"
import { useId, useMemo, useState } from "react"
import { motion, useReducedMotion } from "motion/react"
type Palette = { id: string; name: string; stops: [string, string, string, string] }
type Blend = "overlay" | "soft-light"
const PALETTES: Palette[] = [
{ id: "aurora", name: "Aurora", stops: ["#6366f1", "#8b5cf6", "#22d3ee", "#34d399"] },
{ id: "ember", name: "Ember", stops: ["#fb7185", "#f59e0b", "#f43f5e", "#fb923c"] },
{ id: "tide", name: "Tide", stops: ["#0ea5e9", "#6366f1", "#22d3ee", "#2dd4bf"] },
{ id: "dusk", name: "Dusk", stops: ["#a78bfa", "#f472b6", "#818cf8", "#fbbf24"] },
]
const BLOBS: { pos: string; size: string; dx: number; dy: number; dur: number }[] = [
{ pos: "left-[-8%] top-[-14%]", size: "h-[38rem] w-[38rem]", dx: 52, dy: 34, dur: 19 },
{ pos: "right-[-12%] top-[-8%]", size: "h-[34rem] w-[34rem]", dx: -46, dy: 48, dur: 23 },
{ pos: "left-[16%] bottom-[-20%]", size: "h-[40rem] w-[40rem]", dx: 60, dy: -38, dur: 27 },
{ pos: "right-[4%] bottom-[-16%]", size: "h-[30rem] w-[30rem]", dx: -58, dy: -26, dur: 21 },
]
export default function BgxNoise() {
const reduce = useReducedMotion()
const uid = useId()
const [paletteId, setPaletteId] = useState<string>(PALETTES[0].id)
const [grain, setGrain] = useState<number>(0.55)
const [fineness, setFineness] = useState<number>(0.62)
const [blend, setBlend] = useState<Blend>("overlay")
const [flicker, setFlicker] = useState<boolean>(true)
const palette = PALETTES.find((p) => p.id === paletteId) ?? PALETTES[0]
const grainPct = Math.round(grain * 100)
const noiseUrl = useMemo(() => {
const svg =
"<svg xmlns='http://www.w3.org/2000/svg' width='200' height='200'>" +
"<filter id='n'>" +
"<feTurbulence type='fractalNoise' baseFrequency='" +
fineness.toFixed(3) +
"' numOctaves='2' stitchTiles='stitch'/>" +
"<feColorMatrix type='saturate' values='0'/>" +
"</filter>" +
"<rect width='200' height='200' filter='url(#n)'/>" +
"</svg>"
return `url("data:image/svg+xml,${encodeURIComponent(svg)}")`
}, [fineness])
const grainMoving = flicker && !reduce
const segItem =
"relative cursor-pointer select-none rounded-lg px-3 py-2 text-center text-sm font-medium " +
"text-slate-600 transition-colors dark:text-slate-300"
const segActive =
"peer-checked:bg-white peer-checked:text-slate-900 peer-checked:shadow-sm " +
"dark:peer-checked:bg-slate-700 dark:peer-checked:text-white " +
"peer-focus-visible:ring-2 peer-focus-visible:ring-indigo-500 peer-focus-visible:ring-offset-2 " +
"peer-focus-visible:ring-offset-white dark:peer-focus-visible:ring-offset-slate-900"
return (
<section className="relative flex min-h-[46rem] w-full items-center overflow-hidden bg-slate-50 dark:bg-slate-950">
<style>{`
@keyframes bgxNoise_grain {
0% { background-position: 0 0; }
20% { background-position: -14% 8%; }
40% { background-position: 12% -12%; }
60% { background-position: -8% -6%; }
80% { background-position: 10% 12%; }
100% { background-position: 0 0; }
}
.bgxNoise-grain { animation: bgxNoise_grain 0.5s steps(1, end) infinite; }
@media (prefers-reduced-motion: reduce) {
.bgxNoise-grain { animation: none; }
}
`}</style>
{/* Gradient blob field */}
<div aria-hidden className="absolute inset-0 z-0">
{BLOBS.map((b, i) => (
<motion.div
key={i}
className={`absolute rounded-full opacity-60 blur-3xl will-change-transform dark:opacity-45 ${b.pos} ${b.size}`}
style={{ backgroundColor: palette.stops[i % palette.stops.length] }}
animate={
reduce
? undefined
: { x: [0, b.dx, 0], y: [0, b.dy, 0], scale: [1, 1.12, 1] }
}
transition={{ duration: b.dur, repeat: Infinity, ease: "easeInOut" }}
/>
))}
</div>
{/* Grain overlay */}
<div
aria-hidden
className={`pointer-events-none absolute inset-0 z-[1] ${grainMoving ? "bgxNoise-grain" : ""}`}
style={{
backgroundImage: noiseUrl,
backgroundSize: "200px 200px",
opacity: grain,
mixBlendMode: blend,
}}
/>
{/* Legibility scrim */}
<div
aria-hidden
className="absolute inset-0 z-[2] bg-gradient-to-b from-slate-50/50 via-transparent to-slate-50/70 dark:from-slate-950/50 dark:to-slate-950/80"
/>
{/* Content */}
<div className="relative z-10 mx-auto w-full max-w-6xl px-6 py-24 sm:px-8">
<div className="grid items-center gap-12 lg:grid-cols-[1.05fr_1fr]">
{/* Hero copy */}
<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-semibold uppercase tracking-[0.18em] text-slate-600 backdrop-blur dark:border-slate-700/70 dark:bg-slate-900/50 dark:text-slate-300">
<span className="h-1.5 w-1.5 rounded-full bg-indigo-500" />
Backgrounds — Grain & gradient
</span>
<h1 className="mt-6 text-balance text-4xl font-semibold leading-[1.05] tracking-tight text-slate-900 sm:text-5xl dark:text-white">
Flat gradients look digital.
<br />
<span className="text-slate-500 dark:text-slate-400">Grain makes them feel printed.</span>
</h1>
<p className="mt-6 max-w-md text-pretty text-base leading-relaxed text-slate-600 dark:text-slate-300">
A fine dusting of film grain breaks the banding you get from any CSS gradient,
adds an analog warmth, and holds up from a phone to a 5K panel. Every dial here
re-renders the surface behind this panel in real time — no images, just an SVG
turbulence filter.
</p>
<dl className="mt-8 flex flex-wrap gap-x-8 gap-y-4">
<div>
<dt className="text-xs font-medium uppercase tracking-wide text-slate-500 dark:text-slate-400">
Payload
</dt>
<dd className="mt-0.5 text-lg font-semibold tabular-nums text-slate-900 dark:text-white">
0 KB images
</dd>
</div>
<div>
<dt className="text-xs font-medium uppercase tracking-wide text-slate-500 dark:text-slate-400">
Rendered by
</dt>
<dd className="mt-0.5 text-lg font-semibold text-slate-900 dark:text-white">
feTurbulence
</dd>
</div>
<div>
<dt className="text-xs font-medium uppercase tracking-wide text-slate-500 dark:text-slate-400">
Grain now
</dt>
<dd className="mt-0.5 text-lg font-semibold tabular-nums text-slate-900 dark:text-white">
{grainPct}%
</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-xl sm:p-8 dark:border-slate-700/60 dark:bg-slate-900/60 dark:shadow-black/30">
<div className="flex items-baseline justify-between">
<h2 className="text-lg font-semibold text-slate-900 dark:text-white">Tune the texture</h2>
<span className="text-xs font-medium tabular-nums text-slate-500 dark:text-slate-400">
{palette.name} · {blend === "overlay" ? "Overlay" : "Soft light"}
</span>
</div>
{/* Palette */}
<fieldset className="mt-6">
<legend className="mb-2 block text-sm font-medium text-slate-700 dark:text-slate-200">
Palette
</legend>
<div className="grid grid-cols-2 gap-2 sm:grid-cols-4">
{PALETTES.map((p) => (
<label key={p.id} className="block">
<input
type="radio"
name={`${uid}-palette`}
value={p.id}
checked={paletteId === p.id}
onChange={() => setPaletteId(p.id)}
className="peer sr-only"
/>
<span
className={
"flex flex-col items-center gap-2 rounded-xl border border-slate-200 bg-slate-50/60 p-2.5 " +
"text-xs font-medium text-slate-600 transition-colors " +
"hover:border-slate-300 " +
"peer-checked:border-indigo-500 peer-checked:text-slate-900 " +
"peer-focus-visible:outline-none peer-focus-visible:ring-2 peer-focus-visible:ring-indigo-500 " +
"peer-focus-visible:ring-offset-2 peer-focus-visible:ring-offset-white " +
"dark:border-slate-700 dark:bg-slate-800/40 dark:text-slate-300 dark:hover:border-slate-600 " +
"dark:peer-checked:text-white dark:peer-focus-visible:ring-offset-slate-900"
}
>
<span
className="h-8 w-full rounded-md ring-1 ring-inset ring-black/5"
style={{
backgroundImage: `linear-gradient(135deg, ${p.stops.join(", ")})`,
}}
/>
{p.name}
</span>
</label>
))}
</div>
</fieldset>
{/* Grain amount */}
<div className="mt-6">
<div className="flex items-center justify-between">
<label
htmlFor={`${uid}-grain`}
className="text-sm font-medium text-slate-700 dark:text-slate-200"
>
Grain amount
</label>
<span className="text-sm font-semibold tabular-nums text-slate-500 dark:text-slate-400">
{grainPct}%
</span>
</div>
<input
id={`${uid}-grain`}
type="range"
min={0}
max={1}
step={0.01}
value={grain}
onChange={(e) => setGrain(Number(e.target.value))}
aria-valuetext={`${grainPct} percent`}
className="mt-2 h-2 w-full cursor-pointer appearance-none rounded-full bg-slate-200 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:bg-slate-700 dark:focus-visible:ring-offset-slate-900"
/>
</div>
{/* Grain fineness */}
<div className="mt-5">
<div className="flex items-center justify-between">
<label
htmlFor={`${uid}-fine`}
className="text-sm font-medium text-slate-700 dark:text-slate-200"
>
Grain fineness
</label>
<span className="text-sm font-semibold tabular-nums text-slate-500 dark:text-slate-400">
{fineness.toFixed(2)}
</span>
</div>
<input
id={`${uid}-fine`}
type="range"
min={0.3}
max={1}
step={0.01}
value={fineness}
onChange={(e) => setFineness(Number(e.target.value))}
aria-valuetext={`base frequency ${fineness.toFixed(2)}`}
className="mt-2 h-2 w-full cursor-pointer appearance-none rounded-full bg-slate-200 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:bg-slate-700 dark:focus-visible:ring-offset-slate-900"
/>
<p className="mt-1.5 text-xs text-slate-500 dark:text-slate-400">
Lower is coarse, chunky grain. Higher is fine, sandy grain.
</p>
</div>
{/* Blend mode */}
<fieldset className="mt-6">
<legend className="mb-2 block text-sm font-medium text-slate-700 dark:text-slate-200">
Blend mode
</legend>
<div className="grid grid-cols-2 gap-1 rounded-xl bg-slate-100 p-1 dark:bg-slate-800/70">
{(["overlay", "soft-light"] as Blend[]).map((mode) => (
<label key={mode} className="block">
<input
type="radio"
name={`${uid}-blend`}
value={mode}
checked={blend === mode}
onChange={() => setBlend(mode)}
className="peer sr-only"
/>
<span className={`${segItem} ${segActive}`}>
{mode === "overlay" ? "Overlay" : "Soft light"}
</span>
</label>
))}
</div>
</fieldset>
{/* Flicker switch */}
<div className="mt-6 flex items-center justify-between rounded-xl border border-slate-200 bg-slate-50/60 px-4 py-3 dark:border-slate-700 dark:bg-slate-800/40">
<span className="pr-4">
<span className="block text-sm font-medium text-slate-700 dark:text-slate-200">
Animate grain
</span>
<span className="mt-0.5 block text-xs text-slate-500 dark:text-slate-400">
{reduce
? "Paused — reduced motion is on"
: "Film-style flicker at ~10fps"}
</span>
</span>
<button
type="button"
role="switch"
aria-checked={flicker}
aria-label="Animate grain"
onClick={() => setFlicker((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 " +
(flicker ? "bg-indigo-500" : "bg-slate-300 dark:bg-slate-600")
}
>
<span
className={
"inline-block h-4 w-4 transform rounded-full bg-white shadow transition-transform " +
(flicker ? "translate-x-6" : "translate-x-1")
}
/>
</button>
</div>
{/* Live readout */}
<p
aria-live="polite"
className="mt-5 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs font-medium tabular-nums text-slate-500 dark:text-slate-400"
>
<span className="rounded-md bg-slate-100 px-2 py-1 dark:bg-slate-800">
grain {grainPct}%
</span>
<span className="rounded-md bg-slate-100 px-2 py-1 dark:bg-slate-800">
freq {fineness.toFixed(2)}
</span>
<span className="rounded-md bg-slate-100 px-2 py-1 dark:bg-slate-800">
{blend}
</span>
<span className="rounded-md bg-slate-100 px-2 py-1 dark:bg-slate-800">
{grainMoving ? "animating" : "static"}
</span>
</p>
</div>
</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

Mesh Gradient Background
Originalanimated mesh gradient background

Grid Background
Originalgrid line background with fade

Dots Background
Originaldotted grid background

