A sand-like animated background for React and Tailwind CSS, with an optional gradient overlay for hero sections.
import { SandBackground } from "@/components/sand-background";
return (
<div className="h-svh w-screen relative">
{showGradient && <div
className="absolute z-1 pointer-events-none inset-0"
style={{
background: "radial-gradient(circle at center, transparent 0%, var(--background) 80%)",
}}
/>}
<SandBackground angle={angle} spacing={spacing} />
</div>
)Copy and paste the following code into your project.
import { useMemo } from "react";
function SandBackground({
className,
angle = -38,
spacing = 28,
duration = 6,
animate = true,
}: {
className?: string;
angle?: number;
spacing?: number;
duration?: number;
animate?: boolean;
}) {
const lineColor = "color-mix(in oklab, var(--muted-foreground) 6%, transparent)";
const { x: periodX, y: periodY } = getLinePeriodOffset(angle, spacing);
const animationName = `sand-bg-${String(angle).replace(".", "_")}-${spacing}`;
const backgroundImage = `repeating-linear-gradient(${angle}deg, #5758bb33, transparent ${spacing}px, ${lineColor} ${spacing}px, ${lineColor} ${spacing + 1}px)`;
const keyframes = useMemo(
() => `
@keyframes ${animationName} {
from { transform: translate3d(${periodX}px, ${periodY}px, 0); }
to { transform: translate3d(0, 0, 0); }
}
`,
[animationName, periodX, periodY]
);
return (
<div className={`${className} pointer-events-none absolute inset-0 overflow-hidden`}>
{animate && <style>{keyframes}</style>}
<div
className="absolute inset-[-50%] motion-reduce:paused"
style={{
backgroundImage,
animation: `${animationName} ${duration}s linear infinite`,
}}
/>
</div>
);
}
function getLinePeriodOffset(angle: number, spacing: number) {
const period = spacing + 1;
const rad = (angle * Math.PI) / 180;
return {
x: period * Math.sin(rad),
y: -period * Math.cos(rad),
};
}
export { SandBackground };