Particles

Floating particle field that drifts gently in the background.

Report a bug

Preview

Switch between light and dark to inspect the embedded Storybook preview.

Installation

pnpm dlx shadcn@latest add https://ui.vllnt.ai/r/particles.json

Storybook

Explore all variants, controls, and accessibility checks in the interactive Storybook playground.

View in Storybook

2 stories available:

Code

"use client"; import * as React from "react"; import { cn } from "../../lib/utils"; /** Props for {@link Particles}. */ export type ParticlesProps = React.ComponentPropsWithoutRef<"div"> & { /** Count of drifting dots in the field. Defaults to `30`. */ count?: number; }; type Particle = { delay: number; duration: number; left: number; size: number; top: number; }; function createParticles(count: number): Particle[] { return Array.from({ length: count }, () => ({ delay: Math.random() * 4, duration: 4 + Math.random() * 6, left: Math.random() * 100, size: 2 + Math.random() * 3, top: Math.random() * 100, })); } /** * Decorative field of dots drifting upward at staggered speeds. * * Respects `prefers-reduced-motion`: the dots hold still. * * @example * ```tsx * <Particles count={40} /> * ``` */ export const Particles = ({ className, count = 30, ref, ...props }: ParticlesProps & { ref?: React.Ref<HTMLDivElement> }) => { const [particles] = React.useState(() => createParticles(count)); return ( <div aria-hidden="true" className={cn( "pointer-events-none absolute inset-0 overflow-hidden", className, )} ref={ref} {...props} > {particles.map((particle, index) => ( <span className="absolute rounded-full bg-foreground/30 motion-reduce:animate-none" key={index} style={{ animation: `vllnt-particle-float ${particle.duration}s linear infinite`, animationDelay: `${particle.delay}s`, height: `${particle.size}px`, left: `${particle.left}%`, top: `${particle.top}%`, width: `${particle.size}px`, }} /> ))} </div> ); }; Particles.displayName = "Particles";

Dependencies

  • @vllnt/ui@^0.2.1