Classic Dots Pattern
Flexible dotted background component with configurable color, spacing, scale, and multiple pattern variants, designed to wrap and enhance UI sections with subtle visual texture.
classic-dots-pattern-background-preview.jsx
import ClassicDotsPatternBackground from "@/components/backgrounds/classic-dots-pattern-background/classic-dots-pattern-background";
const ClassicDotsPatternBackgroundPreview = () => {
return (
<ClassicDotsPatternBackground
variant="random"
dotColor="#7a80dd"
style={{
width: "100%",
height: "100%",
}}
wrapperProps = {{
style: {
display: "grid",
placeItems: "center",
height: "100%"
}
}}
>
<h2>
Dot Pattern Background
</h2>
</ClassicDotsPatternBackground>
);
};
export default ClassicDotsPatternBackgroundPreview; Installation
Run the command from your project root directory (the folder that contains package.json).
Terminal / Console
npx mosaicui-cli@latest backgrounds/classic-dots-pattern-background 1. Copy the component file
Create a new file called classic-dots-pattern-background.jsx
in your reusable components folder (for example
/src/components/) and paste the following
code into it.
classic-dots-pattern-background.jsx
import { memo, useState, useRef, useMemo, useCallback, useEffect, useLayoutEffect } from "react";
import styles from "./classic-dots-pattern-background.module.css";
const ClassicDotsPatternBackground = (props) => {
const {
children,
variant = "standard",
dotColor = "rgb(255, 255, 255)",
dotScale = 1,
gap = 4,
// radial variant configuration props
radialDirection = "in",
radialScale = 0.25,
// random variant configuration prop
density = 0.5,
// generic props
className = "",
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 = Math.max(1, globalThis.devicePixelRatio || 1);
return {
devicePixelRatio,
canvasWidth: width * devicePixelRatio,
canvasHeight: height * devicePixelRatio,
};
}, [width, height]);
const _dotColor = useMemo(() => {
const canvas = document.createElement("canvas");
canvas.width = canvas.height = 1;
const ctx = canvas.getContext("2d");
ctx.fillStyle = dotColor;
ctx.fillRect(0, 0, 1, 1);
const [r, g, b] = Array.from(ctx.getImageData(0, 0, 1, 1).data);
canvas.remove();
return (alpha) => {
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
};
}, [dotColor]);
const _dotScale = useMemo(() => (
Math.min(5, Math.max(0.1, dotScale))
), [dotScale]);
const _gap = useMemo(() => (
Math.max(1, gap)
), [gap]);
const _radialScale = useMemo(() => (
Math.min(1, Math.max(0.1, radialScale))
), [radialScale]);
const _density = useMemo(() => (
Math.min(1, Math.max(0.1, density))
), [density]);
const ctx = useMemo(() => {
return canvasRef.current?.getContext("2d");
}, [canvasRef.current]);
const dist = useCallback((x1, y1, x2, y2) => {
return Math.hypot(x2 - x1, y2 - y1);
}, []);
const map = useCallback((value, start1, stop1, start2, stop2) => {
const min = Math.min(start2, stop2);
const max = Math.max(start2, stop2);
const newValue = start2 + (stop2 - start2) * ((value - start1) / (stop1 - start1));
return Math.min(Math.max(newValue, min), max);
}, []);
const render = useCallback(() => {
ctx.setTransform(devicePixelRatio, 0, 0, devicePixelRatio, 0, 0);
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
const dotContainerSize = (_gap * 2) + (_dotScale * 2);
const diagonalDistance = Math.sqrt(width ** 2, height ** 2);
for (let y = 0; y <= height; y += dotContainerSize) {
for (let x = 0; x <= width; x += dotContainerSize) {
if (variant === "random") {
if (Math.random() > _density) {
continue;
}
}
ctx.beginPath();
const posX = x + (dotContainerSize / 2);
const posY = y + (dotContainerSize / 2);
ctx.ellipse(posX, posY, _dotScale, _dotScale, 0, 0, 360, false);
if (variant === "radial") {
const distance = dist(posX, posY, (width / 2), (height / 2));
let alpha = 1;
if (radialDirection === "in") {
alpha = map(distance, 0, (diagonalDistance / (10 * _radialScale)), 1, 0);
} else {
alpha = map(distance, diagonalDistance, (diagonalDistance / (2 / _radialScale)), 1, 0);
}
ctx.fillStyle = _dotColor(alpha);
} else {
ctx.fillStyle = dotColor;
}
ctx.fill();
ctx.closePath();
}
}
}, [
ctx,
map,
dist,
devicePixelRatio,
width,
height,
canvasWidth,
canvasHeight,
variant,
dotColor,
radialDirection,
_dotScale,
_dotColor,
_gap,
_radialScale,
_density,
]);
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["classic-dots-pattern-background"],
].join(" ")}
>
<canvas
aria-hidden={true}
width={canvasWidth}
height={canvasHeight}
ref={canvasRef}
/>
<Wrapper
{...restWrapperProps}
className={[
wrapperClassName,
styles["wrapper"],
].join(" ")}
>
{children}
</Wrapper>
</div>
);
};
export default memo(ClassicDotsPatternBackground); 2. Copy the CSS module file
In the same folder, create a file called classic-dots-pattern-background.module.css and paste the following
CSS.
classic-dots-pattern-background.module.css
.classic-dots-pattern-background {
position: relative;
>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.
Variants
classic-dots-pattern-background
import { useState } from "react";
import ClassicDotsPatternBackground from "@/components/backgrounds/classic-dots-pattern-background/classic-dots-pattern-background";
const ClassicDotsPatternBackgroundVariantsPreview = () => {
const variants = ["standard", "radial", "random"];
const [variant, setVariant] = useState("standard");
return (
<ClassicDotsPatternBackground
variant={variant}
dotColor="#7a80dd"
style={classicDotPatternBackgroundStyles}
wrapperProps = {{
style: wrapperStyles
}}
>
{variants.map(variantType => (
<button
key={variantType}
onClick={() => setVariant(variantType)}
style={{
...buttomStyles,
...(variantType === variant ? activeButtonStyles : null),
}}
>
{variantType}
</button>
))}
</ClassicDotsPatternBackground>
);
};
const classicDotPatternBackgroundStyles = {
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",
};
const activeButtonStyles = {
background: "#a4a8e7",
color: "#111",
};
export default ClassicDotsPatternBackgroundVariantsPreview; Customizations
classic-dots-pattern-background
import { useState } from "react";
import ClassicDotsPatternBackground from "@/components/backgrounds/classic-dots-pattern-background/classic-dots-pattern-background";
const customizations = [
{
variant: "standard",
gap: 2,
},
{
variant: "standard",
dotScale: 5,
gap: 16,
},
{
variant: "radial",
dotScale: 1,
gap: 2,
},
{
variant: "radial",
dotScale: 4,
gap: 2,
},
{
variant: "radial",
dotScale: 2,
gap: 2,
radialDirection: "out",
},
{
variant: "radial",
dotScale: 5,
gap: 1,
radialDirection: "out",
radialScale: 0.65,
},
{
variant: "random",
gap: 2,
density: 0.8,
},
{
variant: "random",
gap: 8,
},
];
const ClassicDotsPatternBackgroundCustomizationsPreview = () => {
const [custmomizationIndex, setCustmomizationIndex] = useState(0);
return (
<ClassicDotsPatternBackground
{...customizations[custmomizationIndex]}
dotColor="#7a80dd"
style={classicDotPatternBackgroundStyles}
wrapperProps = {{
style: wrapperStyles
}}
>
{customizations.map((_, index) => (
<button
key={`customize-${index}`}
onClick={() => setCustmomizationIndex(index)}
style={{
...buttomStyles,
...(index === custmomizationIndex ? activeButtonStyles : null),
}}
>
Pattern {index+1}
</button>
))}
</ClassicDotsPatternBackground>
);
};
const classicDotPatternBackgroundStyles = {
width: "100%",
height: "480px",
padding: "32px",
display: "flex",
justifyContent: "center",
alignItems: "center",
};
const wrapperStyles = {
display: "flex",
maxWidth: "480px",
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",
};
const activeButtonStyles = {
background: "#a4a8e7",
color: "#111",
};
export default ClassicDotsPatternBackgroundCustomizationsPreview; Props
Configure the component with the following props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
| children | React.ReactNode | No | — | Content rendered inside the background wrapper. |
| variant | "standard" | "radial" | "random" | No | "standard" | Determines how dots are distributed in the background. standard renders a grid pattern, radial creates a radial fade effect, and random distributes dots randomly. |
| dotColor | string | No | "rgb(255, 255, 255)" | Color of the dots. Accepts rgb, rgba, or hex. For the radial variant, the alpha channel is used to produce the radial fade effect. |
| dotScale | number | No | 1 | Controls the size of the dots. Recommended range: 0.1 – 5. |
| gap | number | No | 4 | Space between dots in the grid. Minimum value: 1. |
| radialDirection | "in" | "out" | No | "in" | Direction of the radial effect when using the radial variant. in fades toward the center, out fades toward the edges. |
| radialScale | number | No | 0.25 | Intensity/scale of the radial effect. Recommended range: 0.1 – 1. Only applies to the radial variant. |
| density | number | No | 0.5 | Controls how many dots appear in the random variant. Range: 0.1 – 1. Higher values produce denser patterns. |
| className | string | No | — | Additional class names applied to the background container. |
| style | React.CSSProperties | No | — | Inline styles applied to the background container. |
| 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. |