Backgrounds
NoiseSurface
A soft two-colour wash under film grain that flickers. CSS only, no JavaScript.
Download
- React
- Tailwind CSS v4
components/ui/noise-surface.tsx
import type { ReactNode } from "react";
type NoiseSurfaceProps = {
children?: ReactNode;
className?: string;
/** Wash colour in the top left. */
from?: string;
/** Wash colour in the bottom right. */
to?: string;
background?: string;
/** Grain strength, 0 to 1. */
grain?: number;
/** Grain flickers like film. False keeps it still. */
moving?: boolean;
};
const GRAIN = `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E")`;
const CSS = `
@keyframes noise-surface-shift {
0% { transform: translate(0, 0); }
10% { transform: translate(-5%, -10%); }
20% { transform: translate(-15%, 5%); }
30% { transform: translate(7%, -25%); }
40% { transform: translate(-5%, 25%); }
50% { transform: translate(-15%, 10%); }
60% { transform: translate(15%, 0); }
70% { transform: translate(0, 15%); }
80% { transform: translate(3%, 35%); }
90% { transform: translate(-10%, 10%); }
}
.noise-surface-grain { animation: noise-surface-shift 1s steps(1) infinite; }
@media (prefers-reduced-motion: reduce) { .noise-surface-grain { animation: none; } }
`;
/* NoiseSurface: a soft two-colour wash under film grain. CSS only. */
export function NoiseSurface({
children,
className = "",
from = "#a78bfa",
to = "#fb7185",
background = "#0b0912",
grain = 0.2,
moving = true,
}: NoiseSurfaceProps) {
const wash = [
`radial-gradient(120% 90% at 15% 10%, color-mix(in srgb, ${from} 40%, transparent), transparent 60%)`,
`radial-gradient(100% 80% at 90% 100%, color-mix(in srgb, ${to} 35%, transparent), transparent 55%)`,
background,
].join(", ");
return (
<div className={`relative overflow-hidden ${className}`} style={{ background: wash }}>
{moving && <style>{CSS}</style>}
<div
aria-hidden
className={`pointer-events-none absolute -inset-1/2 ${moving ? "noise-surface-grain" : ""}`}
style={{ backgroundImage: GRAIN, opacity: grain }}
/>
<div className="relative h-full">{children}</div>
</div>
);
}
1. Paste the Code tab into
components/ui/noise-surface.tsx. The styles are Tailwind CSS v4 classes.2. Use it. This is the preview above:
app/noise-surface-demo.tsx
import { NoiseSurface } from "@/components/ui/noise-surface"; export default function NoiseSurfaceExample() { return ( <NoiseSurface className="h-full"> <p className="absolute bottom-5 left-6 text-2xl font-medium text-white/90">Shot on film</p> </NoiseSurface> ); }
Props
| Name | Type | Default | What it does |
|---|---|---|---|
| children | ReactNode | — | Content shown over the surface. |
| from | string | "#a78bfa" | Wash colour, top left. |
| to | string | "#fb7185" | Wash colour, bottom right. |
| background | string | "#0b0912" | Base colour. |
| grain | number | 0.2 | Grain strength, 0 to 1. |
| moving | boolean | true | Grain flickers like film. False keeps it still. |
| className | string | "" | Give it a height here. |