WebberUI

Interactive Dot Grid

An interactive dot grid background — a full-bleed regular grid where dots near the cursor scale up and get pushed away, then ease smoothly back.

Loading preview…
npx shadcn@latest add https://webberui.com/r/interactive-dot-grid.json

Playground

Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.

28
1.5
120
<InteractiveDotGrid />

Installation

npx shadcn@latest add https://webberui.com/r/interactive-dot-grid.json

Or, once registries are configured in components.json, install it as @webberui/interactive-dot-grid.

Usage

The component fills a sized parent, with foreground content absolutely positioned on top; give that overlay pointer-events-none so cursor events reach the dot grid:

import { InteractiveDotGrid } from "@/components/ui/interactive-dot-grid";

<div className="relative h-[400px] overflow-hidden rounded-2xl">
  <InteractiveDotGrid />
  <div className="pointer-events-none absolute inset-0 flex items-center justify-center">
    <h2 className="text-2xl font-semibold">Move the cursor to see the grid react</h2>
  </div>
</div>

Adjusting density, influence radius, and color:

<InteractiveDotGrid gap={36} dotRadius={2} influence={160} color="#38bdf8" />

Props

PropTypeDefaultDescription
gapnumber28Spacing between dots (px), which also determines their density
dotRadiusnumber1.5Radius of each dot at rest (px)
influencenumber120Cursor influence radius (px): only dots inside it scale up and get pushed away
colorstringneutral-400 in light / neutral-600 in darkDot color; follows the theme when unspecified
classNamestringAppended to the outermost container's className

How it works

  • Drawn on a single canvas. The entire grid is drawn on one <canvas>; a ResizeObserver reads the container size and sizes the backing buffer by devicePixelRatio (capped at 2×) to avoid blurriness on high-resolution displays
  • Repulsion grows with proximity. Every frame, each dot's distance to the cursor is computed; when it falls inside influence, 1 - dist / influence becomes the strength, which pushes the dot along the opposite direction (by at most about 35% of influence) and scales up its radius
  • Smooth return. Both the cursor coordinates and the overall influence strength interpolate toward their target, so once the cursor leaves the container the strength decays back to 0 and the grid eases naturally back to its baseline positions
  • Idle power saving. Once the animation is at rest and the grid is back at baseline, the rAF loop keeps only the lightweight interpolation math and skips drawing, waking up again the moment the cursor re-enters
  • Theme-aware color. When color is unspecified, the canvas takes its color from currentColor (text-neutral-400 dark:text-neutral-600) and uses a MutationObserver on the <html> element's class to redraw immediately when the light/dark theme is switched
  • Only mouse pointers (pointerType === "mouse") trigger the interaction; touch has no effect

Accessibility

  • The dot grid is a purely decorative background, and the outermost element carries aria-hidden, so it does not interfere with assistive technology announcing the foreground content
  • When the user has "reduce motion" enabled at the system level, only a static grid is drawn — the animation loop never starts and the cursor gets no response
  • On unmount the component cleans up its rAF, ResizeObserver, and MutationObserver, leaving no background timers behind

On this page