WebberUI

Particle Field

An interactive particle field — particles drift freely and link up when they get close, with a cursor that repels or attracts them, drawn on canvas 2D.

Loading preview…
npx shadcn@latest add https://webberui.com/r/particle-field.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.

60
2
0.5
130
<ParticleField />

Installation

npx shadcn@latest add https://webberui.com/r/particle-field.json

Or, once registries are configured in components.json, install it as @webberui/particle-field.

Usage

The particle field is a decorative background: drop it into a relative container and decide the height yourself, then layer foreground content on top:

import { ParticleField } from "@/components/ui/particle-field";

<div className="relative h-[320px] overflow-hidden rounded-xl">
  <ParticleField />
  <div className="pointer-events-none absolute inset-0 flex items-center justify-center">
    <h2 className="text-2xl font-semibold">Hero heading</h2>
  </div>
</div>

Adjusting density, color, and interaction mode:

<ParticleField
  quantity={90}
  color="#22d3ee"
  mode="attract"
  linkDistance={150}
/>

Plain drift with no links:

<ParticleField links={false} interactive={false} />

Props

PropTypeDefaultDescription
quantitynumber60Number of particles; more means denser links, and the performance cost grows as O(n²)
colorstringTheme-aware neutralParticle and link color; when unspecified, neutral-400 in light mode and neutral-600 in dark
particleSizenumber2Radius of each particle at rest (px)
speednumber0.5Drift speed multiplier; per-frame displacement lands roughly within ±speed
linksbooleantrueWhether to draw links between nearby particles
linkDistancenumber130Maximum link distance (px) — the closer the particles, the more solid the line
influencenumber120Cursor influence radius (px); only particles inside it are affected
mode"repel" | "attract""repel"Cursor interaction mode: repel or attract particles
interactivebooleantrueWhether cursor interaction is enabled (mouse pointers only; touch has no effect)
classNamestringAppended to the outermost container's className

How it works

  • A single canvas 2D layer. Every particle and link is drawn on the same <canvas>, cleared with clearRect and redrawn each frame, so there is no explosion of DOM nodes and the frame rate stays stable even with dense links
  • Particles drift freely from a random initial velocity and bounce off the container edges, keeping the overall density even; initial coordinates are generated only inside useEffect (client-side only), so there is no SSR hydration mismatch
  • Cursor interaction works as a "draw-time displacement" rather than a persistent velocity: particles within influence get a displacement computed from their distance — repel pushes them away, attract pulls them in (clamping the displacement so they never cross the cursor). Once the cursor leaves, strength decays smoothly back to 0 and the particles ease back onto their drift path
  • Links are drawn before the particles and fade out with distance (globalAlpha) — the closer they are, the more solid the line. Links are computed from the "position drawn this frame", so the mesh warps along with the repulsion or attraction
  • The real canvas size is set from devicePixelRatio (capped at 2) and then scaled with setTransform, keeping it sharp on Retina displays; a ResizeObserver watches the container and clamps out-of-bounds particles back into range when the size changes
  • When color is unspecified, it reads the canvas's currentColor (resolved from the text-neutral-* class) and uses a MutationObserver on the <html> element's class to re-read the color the moment the light/dark theme is switched

Accessibility

  • The container carries aria-hidden — as a purely decorative background it does not interfere with assistive technology
  • When the user has "reduce motion" enabled at the system level, the rAF loop and cursor interaction are both disabled and only a single static particle field is rendered, with no change to the DOM structure
  • Keep enough contrast in your own foreground content; the particle layer defaults to a low-contrast neutral color and will not hurt the legibility of text above it

On this page