Dot Grid Gradient

A customizable dot grid with gradient scaling, allowing control over dot size, color, and shrink direction.

1. Copy the component file

Create a new file called dot-grid-gradient-background.jsx in your reusable components folder (for example /src/components/) and paste the following code into it.

dot-grid-gradient-background.jsx

import { memo, useState, useRef, useMemo, useCallback, useEffect, useLayoutEffect } from "react";
import styles from "./dot-grid-gradient-background.module.css";

const DotGridGradientBackground = (props) => {
  const {
    children,
    backgroundColor = "rgba(0, 0, 0, 1)",
    dotColor = "rgba(250, 250, 250, 1)",
    dotScale = 0.5,
    direction = "down",
    decay = 0.5,
    className = "",
    style = null,
    wrapperTagName = "div",
    wrapperProps = {},
    ...restProps
  } = props;

  const {
    className: wrapperClassName = "",
    ...restWrapperProps
  } = wrapperProps;

  const Wrapper = wrapperTagName || "div";

  const containerRef = useRef(null);
  const canvasRef = useRef(null);

  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 render = useCallback(() => {
    ctx.setTransform(devicePixelRatio, 0, 0, devicePixelRatio, 0, 0);
    ctx.clearRect(0, 0, canvasWidth, canvasHeight);
    ctx.fillStyle = backgroundColor;
    ctx.fillRect(0, 0, canvasWidth, canvasHeight);

    const dotSize = 5 + (10 * dotScale);
    const k = (1 + (decay * 15)) / (height / dotSize);
    const offset = dotSize / 2;

    const drawDot = (x, y, i, minDotRadius = 0.5) => {
      const posX = x + offset;
      const posY = y + offset;
      const radius = (dotSize / 1.85) - (i * k);
      ctx.beginPath();
      ctx.arc(posX, posY, Math.max(minDotRadius, radius), 0, Math.PI * 2);
      ctx.fillStyle = dotColor;
      ctx.fill();
      ctx.closePath();
    };

    if (["up", "down"].includes(direction)) {
      for (let x = 0; x <= width; x += dotSize) {
        if (direction === "up") {
          for (let y = height; y >= -dotSize; y -= dotSize) {
            drawDot(x, y, (height / dotSize) - (y / dotSize));
          }
        } else {
          for (let y = 0; y <= height; y += dotSize) {
            drawDot(x, y, y / dotSize);
          }
        }
      }
    } else if (["left", "right"].includes(direction)) {
      for (let y = 0; y <= height; y += dotSize) {
        if (direction === "left") {
          for (let x = width; x >= -dotSize; x -= dotSize) {
            drawDot(x, y, (width / dotSize) - (x / dotSize));
          }
        } else {
          for (let x = 0; x <= width; x += dotSize) {
            drawDot(x, y, x / dotSize);
          }
        }
      }
    }
  }, [
    ctx,
    devicePixelRatio,
    width,
    height,
    canvasWidth,
    canvasHeight,
    backgroundColor,
    dotScale,
    dotColor,
    direction,
    decay,
  ]);

  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();
  }, [mounted, render]);

  return (
    <div
      {...restProps}
      ref={containerRef}
      className={[
        className,
        styles["dot-grid-gradient-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(DotGridGradientBackground);

2. Copy the CSS module file

In the same folder, create a file called dot-grid-gradient-background.module.css and paste the following CSS.

dot-grid-gradient-background.module.css

.dot-grid-gradient-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.

dot-grid-gradient-background-preview.jsx

import DotGridGradientBackground from "@/components/backgrounds/dot-grid-gradient-background/dot-grid-gradient-background";

const DotGridGradientBackgroundPreview = () => {
  return (
    <DotGridGradientBackground
      dotColor="#bbb"
      style={{
        width: "100%",
        height: "100%",
      }}
      wrapperProps = {{
        style: {
          display: "grid",
          placeItems: "center",
          height: "100%"
        }
      }}
    >
      <h2 
        style={{
          color: "#fff"
      }}>
        Dot Grid Background
      </h2>
    </DotGridGradientBackground>
  )
};

export default DotGridGradientBackgroundPreview;

Direction

dot-grid-gradient-background

import { useState } from "react";
import DotGridGradientBackground from "@/components/backgrounds/dot-grid-gradient-background/dot-grid-gradient-background";

const DotGridGradientBackgroundDirectionPreview = () => {
  const directions = ["up", "down", "left", "right"];  
  
  const [direction, setDirection] = useState("down");
  
  return (
    <DotGridGradientBackground
      direction={direction}
      style={dotGridBackgroundStyles}
      wrapperProps = {{
        style: wrapperStyles
      }}
    >
      {directions.map(dir => (
        <button
          key={dir}
          onClick={() => setDirection(dir)}
          style={{
            ...buttomStyles,
            ...(dir === direction ? activeButtonStyles : null),
          }}
        >
          {dir}
        </button>
      ))}
    </DotGridGradientBackground>
  )
};

const dotGridBackgroundStyles = {
  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 DotGridGradientBackgroundDirectionPreview;

Dot Scale

Dot Scale: 0.5

dot-grid-gradient-background

import { useState } from "react";
import DotGridGradientBackground from "@/components/backgrounds/dot-grid-gradient-background/dot-grid-gradient-background";

const DotGridGradientBackgroundDotScalePreview = () => {
   const [dotScale, setDotScale] = useState(0.5);
  
  const handleDotScaleChange = (e) => {
    setDotScale(parseFloat(e.target.value));
  };

  return (
    <DotGridGradientBackground
      dotScale={dotScale}
      style={dotGridBackgroundStyles}
      wrapperProps = {{
        style: wrapperStyles,
      }}
    >
      <span style={labelStyles}>
        Dot Scale: {dotScale}
      </span>
      <input
        type="range"
        min="0.1"
        max="1"
        step="0.1"
        value={dotScale}
        onChange={handleDotScaleChange}
      />
    </DotGridGradientBackground>
  )
};

const dotGridBackgroundStyles = {
  width: "100%",
  height: "100%",
  display: "grid",
  placeItems: "center",
};
const wrapperStyles = {
  display: "grid",
  placeItems: "center",
  gap: "8px",
};
const labelStyles = {
  padding: "2px",
  background: "#111",
  color: "#fff",
};

export default DotGridGradientBackgroundDotScalePreview;

Decay

Decay: 0.5

dot-grid-gradient-background

import { useState } from "react";
import DotGridGradientBackground from "@/components/backgrounds/dot-grid-gradient-background/dot-grid-gradient-background";

const DotGridGradientBackgroundDecayPreview = () => {
   const [decay, setDecay] = useState(0.5);
  
  const handleDecayChange = (e) => {
    setDecay(parseFloat(e.target.value));
  };

  return (
    <DotGridGradientBackground
      decay={decay}
      style={dotGridBackgroundStyles}
      wrapperProps = {{
        style: wrapperStyles,
      }}
    >
      <span style={labelStyles}>
        Decay: {decay}
      </span>
      <input
        type="range"
        min="0.1"
        max="1"
        step="0.1"
        value={decay}
        onChange={handleDecayChange}
      />
    </DotGridGradientBackground>
  )
};

const dotGridBackgroundStyles = {
  width: "100%",
  height: "100%",
  display: "grid",
  placeItems: "center",
};
const wrapperStyles = {
  display: "grid",
  placeItems: "center",
  gap: "8px",
};
const labelStyles = {
  padding: "2px",
  background: "#111",
  color: "#fff",
};

export default DotGridGradientBackgroundDecayPreview;

Props

Configure the component with the following props:

PropTypeRequiredDefaultDescription
childrenReactNodeNoContent rendered inside the component wrapper.
backgroundColorstringNo"rgba(0,0,0,1)"Background color of the grid. Accepts any valid CSS color such as rgba() or hex (#000).
dotColorstringNo"rgba(250,250,250,1)"Color used to render the dots. Accepts rgba() or hex values.
dotScalenumberNo0.5Base scale of the dots. Range: 0.11. Controls the overall dot size.
direction"up" | "down" | "left" | "right"No"down"Direction in which the gradient shrink effect is applied.
decaynumberNo0.5Controls how quickly dot sizes decrease across the gradient. Range: 0.11.
classNamestringNo""Additional CSS class names applied to the root element.
styleReact.CSSPropertiesNonullInline styles applied to the root element.
wrapperTagNamestringNo"div"HTML tag used as the wrapper element around the children.
wrapperPropsobjectNo{}Additional props passed to the wrapper element containing the children.