Grid Pattern
A customizable animated grid background component for creating dynamic patterned layouts behind content.
1. Copy the component file
Create a new file called grid-pattern-background.jsx in
your reusable components folder (for example
/src/components/) and paste the following code
into it.
grid-pattern-background.jsx
import { memo, useCallback, useLayoutEffect, useEffect, useRef, useMemo, useState } from "react";
import styles from "./grid-pattern-background.module.css";
const GridPatternBackground = (props) => {
const {
children,
className = "",
backgroundColor = "rgba(0, 0, 0, 1)",
gridColor = "rgba(255, 255, 255, 0.5)",
gridSize = 25,
direction = "down",
speed = 1,
wrapperProps = {},
wrapperTagName = "div",
style = null,
...restProps
} = props;
const {
className: wrapperClassName = "",
...restWrapperProps
} = wrapperProps;
const Wrapper = wrapperTagName || "div";
const containerRef = useRef(null);
const canvasRef = useRef(null);
const rafId = useRef(null);
const translate = useRef({ x: 0, y: 0 });
const [mounted, setMounted] = useState(false);
const [width, setWidth] = useState(0);
const [height, setHeight] = useState(0);
const { devicePixelRatio, canvasWidth, canvasHeight } = useMemo(() => {
const devicePixelRatio = globalThis.devicePixelRatio || 1;
return {
devicePixelRatio,
canvasWidth: width * devicePixelRatio,
canvasHeight: height * devicePixelRatio,
};
}, [width, height]);
const ctx = useMemo(() => {
return canvasRef.current?.getContext("2d");
}, [canvasRef.current]);
const _size = Math.min(Math.max(10, gridSize), 100);
const _speed = Math.min(Math.max(0, speed), 5);
const render = useCallback(() => {
switch(direction) {
case "up":
translate.current.y -= _speed;
break;
case "down":
translate.current.y += _speed;
break;
case "left":
translate.current.x -= _speed;
break;
case "right":
translate.current.x += _speed;
break;
case "top-left":
translate.current.x -= _speed;
translate.current.y -= _speed;
break;
case "top-right":
translate.current.x += _speed;
translate.current.y -= _speed;
break;
case "bottom-left":
translate.current.x -= _speed;
translate.current.y += _speed;
break;
case "bottom-right":
translate.current.x += _speed;
translate.current.y += _speed;
break;
}
translate.current.x %= _size;
translate.current.y %= _size;
ctx.setTransform(devicePixelRatio, 0, 0, devicePixelRatio, 0, 0);
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
for (let i = -_size; i <= (height + _size); i += _size) {
ctx.beginPath();
ctx.moveTo(0, i + translate.current.y);
ctx.lineTo(width, i + translate.current.y);
ctx.strokeStyle = gridColor;
ctx.lineWidth = 1;
ctx.stroke();
ctx.closePath();
}
for (let i = -_size; i <= (width + _size); i += _size) {
ctx.beginPath();
ctx.moveTo(i + translate.current.x, 0);
ctx.lineTo(i + translate.current.x, height);
ctx.strokeStyle = gridColor;
ctx.lineWidth = 1;
ctx.stroke();
ctx.closePath();
}
if(_speed !== 0) {
rafId.current = requestAnimationFrame(render);
}
}, [
ctx,
devicePixelRatio,
width,
height,
canvasWidth,
canvasHeight,
gridColor,
direction,
_size,
_speed
]);
useEffect(() => {
const updateContainerDimensions = () => {
const {
width,
height,
} = containerRef.current.getBoundingClientRect();
setWidth(width);
setHeight(height);
};
const resizeObserver = new ResizeObserver(updateContainerDimensions);
resizeObserver.observe(containerRef.current);
updateContainerDimensions();
setMounted(true);
return () => {
resizeObserver.disconnect();
};
}, []);
useLayoutEffect(() => {
if (!mounted) return;
render();
return () => {
cancelAnimationFrame(rafId.current);
};
}, [mounted, render]);
return (
<div
{...restProps}
ref={containerRef}
className={[
className,
styles["grid-pattern-background"],
].join(" ")}
style={{
...style,
"--background-color": backgroundColor,
}}
>
<canvas
aria-hidden={true}
width={canvasWidth}
height={canvasHeight}
ref={canvasRef}
/>
<Wrapper
{...restWrapperProps}
className={[
wrapperClassName,
styles["wrapper"],
].join(" ")}
>
{children}
</Wrapper>
</div>
);
};
export default memo(GridPatternBackground); 2. Copy the CSS module file
In the same folder, create a file called grid-pattern-background.module.css and paste the following CSS.
grid-pattern-background.module.css
.grid-pattern-background {
position: relative;
background-color: var(--background-color);
>canvas {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 100%;
height: 100%;
z-index: 1;
}
>.wrapper {
position: relative;
z-index: 2;
}
} 3. Use the component
Now you can import and use the component anywhere in your project.
grid-pattern-background-preview.jsx
import GridPatternBackground from "@/components/backgrounds/grid-pattern-background/grid-pattern-background";
const GridPatternBackgroundPreview = () => {
return (
<GridPatternBackground
backgroundColor="#040c1a"
gridColor="#3c4d55"
style={{
width: "100%",
height: "100%",
}}
wrapperProps = {{
style: {
display: "grid",
placeItems: "center",
height: "100%"
}
}}
>
<h2
style={{
color: "#fff"
}}
>
Grid Pattern Background
</h2>
</GridPatternBackground>
);
};
export default GridPatternBackgroundPreview; Direction
grid-pattern-background
import { useState } from "react";
import GridPatternBackground from "@/components/backgrounds/grid-pattern-background/grid-pattern-background";
const GridPatternBackgroundDirectionPreview = () => {
const directions = ["up", "down", "left", "right", "top-left", "top-right", "bottom-left", "bottom-right"];
const [direction, setDirection] = useState("down");
return (
<GridPatternBackground
direction={direction}
backgroundColor="#040c1a"
gridColor="#3c4d55"
style={gridBackgroundStyles}
wrapperProps = {{
style: wrapperStyles,
}}
>
{directions.map(dir => (
<button
key={dir}
onClick={() => setDirection(dir)}
style={{
...buttomStyles,
...(dir === direction ? activeButtonStyles : null),
}}
>
{dir}
</button>
))}
</GridPatternBackground>
);
};
const gridBackgroundStyles = {
width: "100%",
height: "100%",
padding: "24px",
display: "flex",
justifyContent: "center",
alignItems: "center",
};
const wrapperStyles = {
display: "flex",
flexWrap: "wrap",
justifyContent: "center",
alignItems: "center",
gap: "16px",
};
const buttomStyles = {
padding: "8px 16px",
border: "1px solid #fff",
borderRadius: "4px",
background: "transparent",
font: "inherit",
color: "#fff",
textTransform: "uppercase",
};
const activeButtonStyles = {
background: "#fff",
color: "#111",
};
export default GridPatternBackgroundDirectionPreview; Speed
Speed: 1
grid-pattern-background
import { useState } from "react";
import GridPatternBackground from "@/components/backgrounds/grid-pattern-background/grid-pattern-background";
const GridPatternBackgroundSpeedPreview = () => {
const [speed, setSpeed] = useState(1);
const handleSpeedChange = (e) => {
setSpeed(parseFloat(e.target.value));
};
return (
<GridPatternBackground
speed={speed}
backgroundColor="#040c1a"
gridColor="#3c4d55"
style={gridBackgroundStyles}
wrapperProps = {{
style: wrapperStyles,
}}
>
<span style={labelStyles}>
Speed: {speed}
</span>
<input
type="range"
min="0"
max="5"
step="0.1"
value={speed}
onChange={handleSpeedChange}
/>
</GridPatternBackground>
);
};
const gridBackgroundStyles = {
width: "100%",
height: "100%",
display: "grid",
placeItems: "center",
};
const wrapperStyles = {
display: "grid",
placeItems: "center",
gap: "8px",
};
const labelStyles = {
color: "#fff",
};
export default GridPatternBackgroundSpeedPreview; Grid Size
Grid Size: 50
grid-pattern-background
import { useState } from "react";
import GridPatternBackground from "@/components/backgrounds/grid-pattern-background/grid-pattern-background";
const GridPatternBackgroundGridSizePreview = () => {
const [gridSize, setGridSize] = useState(50);
const handleSpeedChange = (e) => {
setGridSize(parseInt(e.target.value));
};
return (
<GridPatternBackground
gridSize={gridSize}
backgroundColor="#040c1a"
gridColor="#3c4d55"
style={gridBackgroundStyles}
wrapperProps = {{
style: wrapperStyles,
}}
>
<span style={labelStyles}>
Grid Size: {gridSize}
</span>
<input
type="range"
min="10"
max="100"
step="1"
value={gridSize}
onChange={handleSpeedChange}
/>
</GridPatternBackground>
);
};
const gridBackgroundStyles = {
width: "100%",
height: "100%",
display: "grid",
placeItems: "center",
};
const wrapperStyles = {
display: "grid",
placeItems: "center",
gap: "8px",
};
const labelStyles = {
color: "#fff",
};
export default GridPatternBackgroundGridSizePreview; Props
Configure the component with the following props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
| children | ReactNode | Yes | — | Content rendered inside the grid background wrapper. |
| className | string | No | — | Additional CSS classes applied to the root container. |
| style | React.CSSProperties | No | null | Inline styles applied to the root container. |
| backgroundColor | string | No | "rgba(0, 0, 0, 1)" | Background color behind the grid pattern. Accepts any valid CSS color. |
| gridColor | string | No | "rgba(255, 255, 255, 0.5)" | Color of the grid lines. Accepts any valid CSS color value. |
| gridSize | number | No | 25 | Size of each grid cell in pixels. Range: 10 – 100. |
| direction | "up" | "down" | "left" | "right" | "top-left" | "top-right" | "bottom-left" | "bottom-right" | No | "down" | Direction in which the grid animation moves. |
| speed | number | No | 1 | Animation speed multiplier. Range: 0 – 5. 0 disables movement. |
| wrapperTagName | string | No | "div" | HTML tag used as the wrapper element around the children. |
| wrapperProps | HTMLAttributes<HTMLElement> | No | {} | Additional props passed to the wrapper element containing the children. |