Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 47 additions & 80 deletions apps/www/registry/magicui/dot-pattern.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
"use client"

import React, { useEffect, useId, useRef, useState } from "react"
import React, { useId } from "react"
import { motion } from "motion/react"

import { cn } from "@/lib/utils"

/**
* DotPattern Component Props
* DotPattern Component Props
*
* @param {number} [width=16] - The horizontal spacing between dots
* @param {number} [height=16] - The vertical spacing between dots
Expand All @@ -16,7 +15,7 @@ import { cn } from "@/lib/utils"
* @param {number} [cy=1] - The y-offset of individual dots
* @param {number} [cr=1] - The radius of each dot
* @param {string} [className] - Additional CSS classes to apply to the SVG container
* @param {boolean} [glow=false] - Whether dots should have a glowing animation effect
* @param {boolean} [glow=false] - Whether the pattern should have a breathing opacity animation
*/
interface DotPatternProps extends React.SVGProps<SVGSVGElement> {
width?: number
Expand All @@ -28,14 +27,14 @@ interface DotPatternProps extends React.SVGProps<SVGSVGElement> {
cr?: number
className?: string
glow?: boolean
[key: string]: unknown
}

/**
* DotPattern Component
*
* A React component that creates an animated or static dot pattern background using SVG.
* The pattern automatically adjusts to fill its container and can optionally display glowing dots.
* An optimized React component that creates a background dot pattern using SVG <pattern>.
* This version is performance-tuned to avoid rendering thousands of individual DOM nodes,
* instead letting the browser's native SVG engine handle the repetition.
*
* @component
*
Expand All @@ -45,6 +44,9 @@ interface DotPatternProps extends React.SVGProps<SVGSVGElement> {
* // Basic usage
* <DotPattern />
*
* // With custom color via Tailwind text utility (fully backward-compatible)
* <DotPattern className="text-red-500" />
*
* // With glowing effect and custom spacing
* <DotPattern
* width={20}
Expand All @@ -55,12 +57,12 @@ interface DotPatternProps extends React.SVGProps<SVGSVGElement> {
*
* @notes
* - The component is client-side only ("use client")
* - Automatically responds to container size changes
* - When glow is enabled, dots will animate with random delays and durations
* - Uses Motion for animations
* - Dots color can be controlled via the text color utility classes
* - Highly performant: uses a single <rect> to render an infinite grid.
* - Responsive: automatically fills its parent container via CSS (inset-0).
* - When glow is enabled, the entire pattern performs a hardware-accelerated opacity transition.
* - Color is controlled via `text-*` Tailwind classes (e.g. `text-red-500`), as
* `<circle fill="currentColor" />` inherits the SVG's CSS `color` property.
*/

export function DotPattern({
width = 16,
height = 16,
Expand All @@ -74,43 +76,9 @@ export function DotPattern({
...props
}: DotPatternProps) {
const id = useId()
const containerRef = useRef<SVGSVGElement>(null)
const [dimensions, setDimensions] = useState({ width: 0, height: 0 })

useEffect(() => {
const updateDimensions = () => {
if (containerRef.current) {
const { width, height } = containerRef.current.getBoundingClientRect()
setDimensions({ width, height })
}
}

updateDimensions()
window.addEventListener("resize", updateDimensions)
return () => window.removeEventListener("resize", updateDimensions)
}, [])

const dots = Array.from(
{
length:
Math.ceil(dimensions.width / width) *
Math.ceil(dimensions.height / height),
},
(_, i) => {
const col = i % Math.ceil(dimensions.width / width)
const row = Math.floor(i / Math.ceil(dimensions.width / width))
return {
x: col * width + cx + x,
y: row * height + cy + y,
delay: Math.random() * 5,
duration: Math.random() * 3 + 2,
}
}
)

return (
<svg
ref={containerRef}
aria-hidden="true"
className={cn(
"pointer-events-none absolute inset-0 h-full w-full text-neutral-400/80",
Expand All @@ -119,40 +87,39 @@ export function DotPattern({
{...props}
>
<defs>
<radialGradient id={`${id}-gradient`}>
<stop offset="0%" stopColor="currentColor" stopOpacity="1" />
<stop offset="100%" stopColor="currentColor" stopOpacity="0" />
</radialGradient>
<pattern
id={id}
width={width}
height={height}
patternUnits="userSpaceOnUse"
x={x}
y={y}
>
<circle cx={cx} cy={cy} r={cr} fill="currentColor" />
</pattern>
</defs>
{dots.map((dot) => (
<motion.circle
key={`${dot.x}-${dot.y}`}
cx={dot.x}
cy={dot.y}
r={cr}
fill={glow ? `url(#${id}-gradient)` : "currentColor"}
initial={glow ? { opacity: 0.4, scale: 1 } : {}}
animate={
glow
? {
opacity: [0.4, 1, 0.4],
scale: [1, 1.5, 1],
}
: {}
}
transition={
glow
? {
duration: dot.duration,
repeat: Infinity,
repeatType: "reverse",
delay: dot.delay,
ease: "easeInOut",
}
: {}
}
/>
))}

<motion.rect
width="100%"
height="100%"
fill={`url(#${id})`}
animate={
glow
? {
opacity: [0.3, 0.8, 0.3],
}
: {}
}
transition={
glow
? {
duration: 4,
repeat: Infinity,
ease: "easeInOut",
}
: {}
}
/>
</svg>
)
}
}