Particle Constellation
Show drifting, connected particles that gather around the pointer.
import { ParticleConstellation, SignalOptions } from '@youj/motion/backgrounds'
Installation & usage
npm i @youj/motionFor an app already set up with npx @youj/cli init, which installs the peers (@youj/core, @youj/motion, motion and Base UI) and wires the provider. New app? Start at Installation.
Use it where the idea is connection: service graphs, networks, integrations, collaboration. It reads as a living diagram, and it's the one background that answers the pointer directly. Don't use it behind reading-length text, since the hairlines cross everything, and keep it to one per page.
The constellation fills its nearest positioned ancestor and paints behind your content, so make the container relative and isolate.
import { ParticleConstellation } from '@youj/motion/backgrounds'
export function GraphHero() {
return (
<section className="relative isolate min-h-[560px]">
<ParticleConstellation />
<p className="relative text-5xl font-semibold">See every dependency before it breaks</p>
</section>
)
}Each particle drifts at its own slow velocity and wraps around the edges. Two particles closer than linkDistance are joined by a hairline that fades with distance. The number of particles follows the area: at density={1}, one particle per 9,000 square pixels, capped at 900. A phone and a wide display look equally full.
Description
A hero section over the particle graph; move the pointer across it and nearby links light up.
colors takes three CSS colors: the particles, the links, then the highlight for links and particles near the pointer. The defaults are --color-fg-muted, --color-fg-subtle and --color-accent. Token references follow the theme, and the constellation rereads them when the theme changes.
A switch between particles that gather around the pointer and particles that part around it.
The pointer is a node of its own. It links to every particle in reach and lights the links around it in the highlight color. With pointer="attract" (the default), particles gather into a loose ring around it instead of collapsing onto the cursor. With "repel", they part around it. When the pointer leaves, the particles ease back to their own drift.
The pointer is tracked on the window, so the field responds under the content above it. Touch doesn't gather particles, because it produces no hover. Set interactive={false} where the field sits behind something people work in.
Pulses of light firing along the links, like neurons.
Set signals and a node fires: a small bright pulse with a short tail runs down a link to the next node, which brightens and sometimes fires on, so the signal cascades. Each chain takes one color of a soft rainbow (mint, sky, violet and orange), brighter on a dark canvas. Pass { colors, rate } to choose the CSS colors and how many chains start per second. The number of pulses in flight is capped, lower on the low motion tier, and none run under reduced motion.
A sparse, still constellation behind an empty state, with no pointer response.
At a low density and intensity, and without pointer response, the constellation becomes a hint of structure behind an empty state: the graph to come, not a show.
Code
particle-constellation-demo.tsx, the Demo story. The component itself comes from the package.
Reference
Propscolors, intensity, speed… (9)
Props
| Prop | Type | |
|---|---|---|
colors | [particles: string, links: string, highlight: string][particles, links, highlight] CSS colors. Default: | – |
intensity | numberOpacity of particles and links, 0–1.5. default 1 | 1 |
speed | numberDrift speed multiplier. default 1 | 1 |
density | numberParticles per area, relative. 1 ≈ one particle per 9 000 CSS px². default 1 | 1 |
linkDistance | numberMax link length in CSS px. default 130 | 130 |
pointer | 'attract' | 'repel'How particles respond to the pointer. default 'attract' | 'attract' |
interactive | booleanPointer reactivity. default true | true |
signals | boolean | SignalOptionsPulses of light that travel along the links from particle to particle, sometimes triggering the next one,
like neurons firing. default false | false |
className | string | – |
PerformanceIt's drawn with Canvas 2D, not WebGL
Performance
It's drawn with Canvas 2D, not WebGL. A spatial grid sized to the link distance means each particle only checks its neighboring cells, and links are batched into a handful of strokes per frame by opacity. The canvas renders at a device-pixel ratio capped at 2. The loop pauses when the constellation is offscreen or the tab is hidden, and it stops and cleans up on unmount. If the browser can't create a 2D context, it falls back to a static CSS wash of the highlight color.
MotionDrift, pointer pull, pointer strength · reduced motion
Motion
| Moment | Tokens | ||
|---|---|---|---|
| Driftper-particle velocity, wrapping at the edges | no token speed ÷ time scale | speed ÷ time scale | |
| Pointer pullattract or repel within reach, then relax | no token per-frame physics | per-frame physics | |
| Pointer strengtheases in and out with the pointer | no token smoothed per frame | smoothed per frame |
Reduced motion
Draws one fixed, seeded constellation: the same composition every time. There's no drift and no pointer response, and it redraws only when the size, the props or the theme change.
Reduced motion
The constellation shows a fixed composition from a seeded layout, so it's the same on every visit and in every screenshot. The particles and links keep their colors and density. Only the drift and the pointer response are gone. Switch on Reduced motion in any preview above to compare.
AccessibilityARIA
Accessibility
- It's decorative: the element is
aria-hiddenand ignores pointer events. - Hairlines cross whatever sits above them. Keep a quiet pool of canvas behind the copy, as the hero preview does, or lower
intensity, and check your text's contrast there. - The drift is slow and stops offscreen. Under reduced motion the field doesn't move at all.