Warm, textured, alive
Customize
Usage
import { GrainGradient } from "@/components/motionkit/grain-gradient";
<GrainGradient
className="h-96 rounded-3xl"
>
<h1>Your hero</h1>
</GrainGradient>Motion
The exact curves this component runs, plotted with Motion's own maths.
Drift
Runs on its own
Works in the App Router as is. Requires Tailwind CSS v4.
npm install motionSave as components/motionkit/grain-gradient.tsx.
"use client";
import type { ReactNode } from "react";
import { motion, useReducedMotion } from "motion/react";
type GrainGradientProps = {
children?: ReactNode;
/** Three colours that drift into each other. */
colors?: [string, string, string];
/** 0–1. How visible the film grain is. */
grain?: number;
/** Seconds for one full drift cycle. */
duration?: number;
className?: string;
};
// fractal noise as a tiny inline SVG, so the grain needs no image file
const NOISE =
"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\")";
export function GrainGradient({
children,
colors = ["#0E7C86", "#A56D5B", "#3D1C13"],
grain = 0.22,
duration = 18,
className = "",
}: GrainGradientProps) {
const reduceMotion = useReducedMotion();
const [a, b, c] = colors;
return (
<div className={`relative isolate overflow-hidden bg-[#0C1010] ${className}`}>
<motion.div
aria-hidden
className="absolute -inset-1/4 -z-20"
style={{
backgroundImage: `radial-gradient(40% 50% at 20% 30%, ${a}, transparent 70%), radial-gradient(45% 55% at 80% 40%, ${b}, transparent 70%), radial-gradient(50% 50% at 50% 90%, ${c}, transparent 70%)`,
}}
animate={reduceMotion ? undefined : { rotate: [0, 8, -6, 0], scale: [1, 1.08, 1.02, 1], x: ["0%", "4%", "-3%", "0%"] }}
transition={{ duration, repeat: Infinity, ease: "easeInOut" }}
/>
<div
aria-hidden
className="pointer-events-none absolute inset-0 -z-10 mix-blend-overlay"
style={{ backgroundImage: NOISE, opacity: grain }}
/>
{children}
</div>
);
}
| Prop | Type | Default | Description |
|---|---|---|---|
| colors | [string, string, string] | ["#0E7C86", "#A56D5B", "#3D1C13"] | Three colours that drift into each other. |
| grain | number | 0.22 | 0–1. How visible the film grain is. |
| duration | number | 18 | Seconds for one full drift cycle. |
Ambient by default
Aurora Background
Drifting, blurred color fields for hero sections and empty states.
Look up
Star Field
A slow-drifting night sky with twinkling stars, for space, launch and AI product heroes.
Into the future
Horizon Grid
A retro perspective grid rolling toward a glowing horizon, synthwave style.