An animated background grid pattern made with SVGs.
1import { AnimatedGridPattern } from "@/components/backgrounds/pxl/animated-grid-pattern";2import { cn } from "@/lib/utils";3
4export default function AnimatedGridPatternDemo() {5 return (6 <AnimatedGridPattern7 numSquares={30}8 maxOpacity={0.1}9 duration={3}10 repeatDelay={1}11 className={cn(12 "mask-[radial-gradient(500px_circle_at_center,white,transparent)]",13 "inset-x-0 inset-y-[-30%] h-[200%] skew-y-12",14 )}15 />16 );17}Installation
pnpm dlx shadcn@latest add pxl-ui/registry/backgrounds/animated-grid-pattern
Install the following dependencies:
pnpm add motion
Copy and paste the following code into your project.
1"use client";2
3import { cva, type VariantProps } from "class-variance-authority";4import { motion } from "motion/react";5import {6 type ComponentPropsWithoutRef,7 useCallback,8 useEffect,9 useId,10 useRef,11 useState,12} from "react";13
14import { cn } from "@/lib/utils";15
16const animatedGridPatternVariants = cva("pointer-events-none absolute inset-0 h-full w-full", {17 variants: {18 variant: {19 default: "bg-background fill-border stroke-border text-border",20 primary: "bg-primary fill-primary-foreground/30 stroke-primary-foreground/30 text-primary-foreground/30",21 secondary: "bg-secondary fill-secondary-foreground/30 stroke-secondary-foreground/30 text-secondary-foreground/30",22 muted: "bg-muted fill-muted-foreground/30 stroke-muted-foreground/30 text-muted-foreground/30",23 info: "bg-info fill-info-foreground/30 stroke-info-foreground/30 text-info-foreground/30",24 success: "bg-success fill-success-foreground/30 stroke-success-foreground/30 text-success-foreground/30",25 warning: "bg-warning fill-warning-foreground/30 stroke-warning-foreground/30 text-warning-foreground/30",26 danger: "bg-danger fill-danger-foreground/30 stroke-danger-foreground/30 text-danger-foreground/30",27 },28 },29 defaultVariants: {30 variant: "default",31 },32});33
34type Square = {35 id: number;36 pos: [number, number];37 iteration: number;38};39
40function AnimatedGridPattern({41 className,42 duration = 4,43 height = 40,44 maxOpacity = 0.5,45 numSquares = 50,46 repeatDelay = 0.5,47 strokeDasharray = 0,48 variant = "default",49 width = 40,50 x = -1,51 y = -1,52 ...props53}: ComponentPropsWithoutRef<"svg"> &54 VariantProps<typeof animatedGridPatternVariants> & {55 width?: number;56 height?: number;57 x?: number;58 y?: number;59 strokeDasharray?: number;60 numSquares?: number;61 maxOpacity?: number;62 duration?: number;63 repeatDelay?: number;64 }) {65 const id = useId();66 const containerRef = useRef<SVGSVGElement | null>(null);67 const [dimensions, setDimensions] = useState({ width: 0, height: 0 });68 const [squares, setSquares] = useState<Array<Square>>([]);69
70 const getPos = useCallback((): [number, number] => {71 return [72 Math.floor((Math.random() * dimensions.width) / width),73 Math.floor((Math.random() * dimensions.height) / height),74 ];75 }, [dimensions.height, dimensions.width, height, width]);76
77 const generateSquares = useCallback(78 (count: number) => {79 return Array.from({ length: count }, (_, i) => ({80 id: i,81 pos: getPos(),82 iteration: 0,83 }));84 },85 [getPos],86 );87
88 const updateSquarePosition = useCallback(89 (squareId: number) => {90 setSquares((currentSquares) => {91 const current = currentSquares[squareId];92 if (!current || current.id !== squareId) return currentSquares;93
94 const nextSquares = currentSquares.slice();95 nextSquares[squareId] = {96 ...current,97 pos: getPos(),98 iteration: current.iteration + 1,99 };100
101 return nextSquares;102 });103 },104 [getPos],105 );106
107 useEffect(() => {108 if (dimensions.width && dimensions.height) {109 setSquares(generateSquares(numSquares));110 }111 }, [dimensions.width, dimensions.height, generateSquares, numSquares]);112
113 useEffect(() => {114 const element = containerRef.current;115 let resizeObserver: ResizeObserver | null = null;116
117 if (element) {118 resizeObserver = new ResizeObserver((entries) => {119 for (const entry of entries) {120 setDimensions((currentDimensions) => {121 const nextWidth = entry.contentRect.width;122 const nextHeight = entry.contentRect.height;123 if (124 currentDimensions.width === nextWidth &&125 currentDimensions.height === nextHeight126 ) {127 return currentDimensions;128 }129 return { width: nextWidth, height: nextHeight };130 });131 }132 });133
134 resizeObserver.observe(element);135 }136
137 return () => {138 if (resizeObserver) {139 resizeObserver.disconnect();140 }141 };142 }, []);143
144 return (145 <svg146 ref={containerRef}147 aria-hidden="true"148 className={cn(animatedGridPatternVariants({ variant }), className)}149 {...props}150 >151 <defs>152 <pattern153 id={id}154 width={width}155 height={height}156 patternUnits="userSpaceOnUse"157 x={x}158 y={y}159 >160 <path161 d={`M.5 ${height}V.5H${width}`}162 fill="none"163 strokeDasharray={strokeDasharray}164 />165 </pattern>166 </defs>167 <rect width="100%" height="100%" fill={`url(#${id})`} />168 <svg x={x} y={y} className="overflow-visible">169 {squares.map(({ pos: [squareX, squareY], id, iteration }, index) => (170 <motion.rect171 initial={{ opacity: 0 }}172 animate={{ opacity: maxOpacity }}173 transition={{174 duration,175 repeat: 1,176 delay: index * 0.1,177 repeatType: "reverse",178 repeatDelay,179 }}180 onAnimationComplete={() => updateSquarePosition(id)}181 key={`${id}-${iteration}`}182 width={width - 1}183 height={height - 1}184 x={squareX * width + 1}185 y={squareY * height + 1}186 fill="currentColor"187 strokeWidth="0"188 />189 ))}190 </svg>191 </svg>192 );193}194
195export { AnimatedGridPattern };Update the import paths to match your project setup.
import { AnimatedGridPattern } from "@/components/backgrounds/pxl/animated-grid-pattern"<AnimatedGridPattern />Variants
Use the variant prop to change the colors of the grid.
1"use client";2
3import { AnimatedGridPattern } from "@/components/backgrounds/pxl/animated-grid-pattern";4import { cn } from "@/lib/utils";5
6export default function FlickeringGridVariants() {7 return (8 <div className="flex flex-wrap size-full items-center justify-center gap-2">9 <div className="relative w-max h-32 aspect-video pixel-rounded pixel-size-lg">10 <AnimatedGridPattern11 variant="muted"12 numSquares={30}13 maxOpacity={0.1}14 duration={3}15 repeatDelay={1}16 className={cn(17 "mask-[radial-gradient(500px_circle_at_center,white,transparent)]",18 "inset-x-0 inset-y-[-30%] h-[200%] skew-y-12",19 )}20 />21 </div>22 <div className="relative w-max h-32 aspect-video pixel-rounded pixel-size-lg">23 <AnimatedGridPattern24 variant="primary"25 numSquares={30}26 maxOpacity={0.1}27 duration={3}28 repeatDelay={1}29 className={cn(30 "mask-[radial-gradient(500px_circle_at_center,white,transparent)]",31 "inset-x-0 inset-y-[-30%] h-[200%] skew-y-12",32 )}33 />34 </div>35 <div className="relative w-max h-32 aspect-video pixel-rounded pixel-size-lg">36 <AnimatedGridPattern37 variant="info"38 numSquares={30}39 maxOpacity={0.1}40 duration={3}41 repeatDelay={1}42 className={cn(43 "mask-[radial-gradient(500px_circle_at_center,white,transparent)]",44 "inset-x-0 inset-y-[-30%] h-[200%] skew-y-12",45 )}46 />47 </div>48 <div className="relative w-max h-32 aspect-video pixel-rounded pixel-size-lg">49 <AnimatedGridPattern50 variant="success"51 numSquares={30}52 maxOpacity={0.1}53 duration={3}54 repeatDelay={1}55 className={cn(56 "mask-[radial-gradient(500px_circle_at_center,white,transparent)]",57 "inset-x-0 inset-y-[-30%] h-[200%] skew-y-12",58 )}59 />60 </div>61 <div className="relative w-max h-32 aspect-video pixel-rounded pixel-size-lg">62 <AnimatedGridPattern63 variant="warning"64 numSquares={30}65 maxOpacity={0.1}66 duration={3}67 repeatDelay={1}68 className={cn(69 "mask-[radial-gradient(500px_circle_at_center,white,transparent)]",70 "inset-x-0 inset-y-[-30%] h-[200%] skew-y-12",71 )}72 />73 </div>74 <div className="relative w-max h-32 aspect-video pixel-rounded pixel-size-lg">75 <AnimatedGridPattern76 variant="danger"77 numSquares={30}78 maxOpacity={0.1}79 duration={3}80 repeatDelay={1}81 className={cn(82 "mask-[radial-gradient(500px_circle_at_center,white,transparent)]",83 "inset-x-0 inset-y-[-30%] h-[200%] skew-y-12",84 )}85 />86 </div>87 </div>88 );89}API Reference
AnimatedGridPattern
| Prop | Type | Default | Description |
|---|---|---|---|
className |
string |
- |
Additional classes to be added to the pattern |
variant |
"default" | "primary" | "secondary" | "muted" | "success" | "warning" | "danger" |
"default" |
Colors of the grids |
width |
number |
40 |
Width of the pattern |
height |
number |
40 |
Height of the pattern |
x |
number |
-1 |
X offset of the pattern |
y |
number |
-1 |
Y offset of the pattern |
strokeDasharray |
number |
0 |
Stroke dash array of the pattern |
numSquares |
number |
200 |
Number of squares in the pattern |
maxOpacity |
number |
0.5 |
Maximum opacity of the pattern |
duration |
number |
1 |
Duration of the animation |
repeatDelay |
number |
0.5 |
Repeat delay of the animation |