Grid Pattern

A customizable animated grid background component for creating dynamic patterned layouts behind content.

grid-pattern-background-preview.jsx

import GridPatternBackground from "@/components/backgrounds/grid-pattern-background/grid-pattern-background";

const GridPatternBackgroundPreview = () => {
  return (
    <GridPatternBackground
      gridColor="rgba(162,201,229,0.5)"
      style={{
        width: "100%",
        height: "100%",
      }}
      wrapperProps = {{
        style: {
          display: "grid",
          placeItems: "center",
          height: "100%"
        }
      }}
    >
      <h2>
        Grid Pattern Background
      </h2>
    </GridPatternBackground>
  );
};

export default GridPatternBackgroundPreview;

Installation

Run the command from your project root directory (the folder that contains package.json).

Terminal / Console

npx mosaicui-cli@latest backgrounds/grid-pattern-background

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, 0)", 
    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 = Math.max(1, 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;
  }
}

Examples

Discover how supported props transform components to match your needs.

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}
      gridColor="rgba(162,201,229,0.5)"
      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 var(--text-primary)",
  borderRadius: "4px",
  background: "var(--layer-tertiary)",
  font: "inherit",
  color: "var(--text-primary)",
  textTransform: "uppercase",
  cursor: "pointer",
};
const activeButtonStyles = {
  background: "rgba(162,201,229,1)",
  color: "#111",
};

export default GridPatternBackgroundDirectionPreview;

Speed

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}
      gridColor="rgba(162,201,229,0.5)"
      style={gridBackgroundStyles}
      wrapperProps = {{
        style: wrapperStyles,
      }}
    >
      <label
        htmlFor="grid-pattern-background-speed-input"
        style={labelStyles}
      >
        Speed: {speed}
      </label>
      <input
        type="range"
        min="0"
        max="5"
        step="0.1"
        id="grid-pattern-background-speed-input"
        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 = {
  padding: "2px 4px",
  background: "#111",
  color: "#fff",
  borderRadius: "4px",
};

export default GridPatternBackgroundSpeedPreview;

Grid Size

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}
      gridColor="rgba(162,201,229,0.5)"
      style={gridBackgroundStyles}
      wrapperProps = {{
        style: wrapperStyles,
      }}
    >
      <label
        htmlFor="grid-pattern-background-grid-size-input"
        style={labelStyles}
      >
        Grid Size: {gridSize}
      </label>
      <input
        type="range"
        min="10"
        max="100"
        step="1"
        id="grid-pattern-background-grid-size-input"
        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 = {
  padding: "2px 4px",
  background: "#111",
  color: "#fff",
  borderRadius: "4px",
};

export default GridPatternBackgroundGridSizePreview;

Props

Configure the component with the following props:

PropTypeRequiredDefaultDescription
childrenReactNodeYesContent rendered inside the grid background wrapper.
classNamestringNoAdditional CSS classes applied to the root container.
styleReact.CSSPropertiesNonullInline styles applied to the root container.
backgroundColorstringNo"rgba(0, 0, 0, 0)"Background color behind the grid pattern. Accepts any valid CSS color.
gridColorstringNo"rgba(255, 255, 255, 0.5)"Color of the grid lines. Accepts any valid CSS color value.
gridSizenumberNo25Size 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.
speednumberNo1Animation speed multiplier. Range: 0 – 5. 0 disables movement.
wrapperTagNamestringNo"div"HTML tag used as the wrapper element around the children.
wrapperPropsHTMLAttributes<HTMLElement>No{}Additional props passed to the wrapper element containing the children.