Page Sections

Particle Field Hero

A hero over an interactive particle field: particles drift and link up when close, the pointer pushes them away, and the headline stays legible on top.

A full-bleed canvas particle field fills the background. Each particle gets its starting position and direction from a seed, drifts 0.15–0.4px per frame and bounces off the edges; any two particles closer than linkDistance are joined by an accent-colored line that fades with distance. When the pointer comes within 120px it pushes particles away, and they slip back into their drift once it leaves. A radial mask fading from the background color to transparent sits under the headline, so the text keeps its contrast however dense the field gets. Optionally, a glass-framed photo card sits on the right.

Bring this into your project

Run this command in your project folder. The CLI adds the source and dependencies; then follow the usage example below.

npx webberui@latest add particle-field-hero.en

New project or installation trouble? Start here →

Playground

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

80
110
1
600
Active edited usageDefault props
<ParticleFieldHero/>

The complete example includes the preview layout and data. Install the component first, then paste this file into your project.

Installation

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

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

Usage

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

<ParticleFieldHero
  eyebrow="潮汐感測平台 · 公開測試中"
  title="把每一個感測點連成一張網"
  subtitle="千台裝置的即時讀數在同一個畫面流動。"
  primaryAction={{ label: "申請試用", href: "/signup" }}
  secondaryAction={{ label: "看架構說明", href: "/docs" }}
  count={100}
  linkDistance={120}
  showMedia
  images={[{ src: "/hero/sensor.jpg", alt: "海岸邊的感測站" }]}
/>

Props

PropTypeDefaultDescription
titleReactNode—Headline (required)
eyebrowReactNode—Small label above the headline, shown in the accent color
subtitleReactNode—Supporting text below the headline
primaryActionParticleFieldHeroAction—Primary CTA with an accent background and an arrow icon
secondaryActionParticleFieldHeroAction—Secondary CTA, outlined
as"h1" | "h2""h1"Heading level; use "h2" when the page already has an h1
minHeightnumber560Minimum section height (px)
align"center" | "left""center"Text alignment; always left when showMedia is on
countnumber80Maximum particle count; the actual count is capped at one per 12,000px² of area
linkDistancenumber110Particles closer than this (px) get a link whose opacity fades with distance; 0 disables links
repelbooleantruePush particles away from the pointer within 120px
speednumber—Particle speed multiplier, written to --wb-particle-field-hero-speed; falls back to the CSS variable (default 1)
durationnumber—Text entrance duration (ms), written to --wb-particle-field-hero-duration; falls back to the CSS variable (default 600)
seednumber1Random seed for initial positions and directions; the same seed gives the same layout
showMediabooleanfalseShow a glass-framed photo card on the right
imagesParticleFieldHeroImage[]—Photo card image (only the first is used); without src a gradient + SVG geometric placeholder is drawn
classNamestring—Merged into the root section

ParticleFieldHeroAction

FieldTypeDefaultDescription
labelstring—Button text
hrefstring—Renders an <a> when provided, otherwise a <button type="button">
onClick(event: React.MouseEvent<HTMLElement>) => void—Click handler

ParticleFieldHeroImage

FieldTypeDefaultDescription
srcstring—Your own image URL; a placeholder is shown when omitted
altstring—Alt text (required)

How it works

  • Particles come from a mulberry32 PRNG seeded with seed, so positions and directions are identical every time; changing speed does not reseed, so nothing jumps.
  • The repel is only a draw-time offset that is never written back to the particle, so once the pointer leaves, particles carry on along their original path. Pointer position and repel strength ease at 0.12 per frame for smooth entry and exit. Only mouse and pen respond; touch drags don't trigger it.
  • Colors are read with getComputedStyle inside the effect: particles use the canvas currentColor (neutral-400 light, neutral-500 dark) and links use the accent at 25% opacity; both are re-read when the theme changes.
  • Canvas resolution is capped at DPR 2 and follows its size through a ResizeObserver; rAF pauses when the field scrolls out of view (IntersectionObserver) or the tab is hidden (visibilitychange), and everything is cleaned up on unmount.
  • Overridable CSS variables: --wb-particle-field-hero-accent (default #0d9488, #2dd4bf in dark mode), --wb-particle-field-hero-speed, --wb-particle-field-hero-duration.
  • Entrance: the canvas fades in over 800ms; eyebrow, headline, subtitle and CTAs rise 16px and fade in, 80ms apart, easing cubic-bezier(0.22,1,0.36,1).

Accessibility

  • The root is a <section aria-labelledby> pointing at the headline's useId.
  • The canvas and radial mask are aria-hidden and purely decorative; the placeholder photo card uses role="img" with its alt text.
  • CTAs are <a> elements or type="button" buttons with a focus-visible ring.
  • With "reduce motion" enabled, a single static frame of particles and links is drawn: no rAF loop and no repel. The text entrance becomes a 150ms fade with no stagger.