Concepts
How the field thinks.
field-ui is one particle field that the page lives inside. Elements are bodies that bend it; the field's density bends them back. Everything else is a way to shape that one reciprocal loop.
Bodies
A body is any element you mark with data-body. Its value is
one or more force tokens, space-separated — they compose, so a single element
can attract and swirl at once. The engine re-reads each body's bounding box every frame,
so moving the element moves the force.
<a
data-body="attract swirl" // forces compose, space-separated
data-strength="0.9"
data-range="320"
data-color="#4da3ff"
data-when="active" // gate: only acts while engaged
data-feedback // opt into density write-back
>link</a>
Common attributes — data-strength, data-range,
data-color, data-spin, data-angle — are read by the
forces that use them; each force ignores the ones it doesn't. See the
attribute reference for the full matrix.
The single field
There is exactly one field — a fixed, full-viewport <canvas> behind your
content, running one requestAnimationFrame loop over a conserved pool of
particles (~130 at default density). Every body acts on that same pool. This is the
central law: nothing is created from nothing. Matter is moved, captured,
and released — a sink that swallows particles must later release them; the reservoir
keeps the count constant. Effects can't cheat by spawning matter, which is what makes the
field feel physical rather than decorative.
The DOM ⇄ canvas binding
The rarest property of the system is a two-way binding between layout and a physics sim. Most particle backgrounds are one-way (canvas reacts to the mouse). Here the DOM is the simulation's geometry, and the simulation writes back into layout and type. Two pipes, every frame:
// DOM → field (every frame, per body)
cx = (rect.left + rect.width / 2) * DPR
cy = (rect.top + rect.height / 2) * DPR
// field → DOM (every frame, per data-feedback body)
element.style.setProperty('--d', density) // d ∈ [0, 1] Pipe 1 (geometry in): any DOM change — reflow, transform, resize — is an
instantaneous change to the force geometry, so animating the DOM animates the sim for free.
Pipe 2 (density out): the field samples how much matter pooled on each
data-feedback body and writes it back as --d.
Reciprocity & --d
Reciprocity is the payoff. Opt a body in with data-feedback and the engine
eases the gathered density into a CSS custom property --d ∈ [0, 1] on the
element. Your styles turn that number into anything — weight, glow, scale, colour:
.headline {
/* the engine writes --d; CSS turns density into weight + glow */
font-variation-settings: 'wght' calc(380 + var(--d) * 420);
text-shadow: 0 0 calc(var(--d) * 40px) rgba(77, 163, 255, calc(var(--d)));
} The value is low-pass filtered (a ~0.2s time constant), so it eases rather than jitters. This is the sanctioned way to make type react — words gain weight where matter collects. (The engine never assembles particles into letterforms; words glow and grow, they are not drawn from dots.)
The orthogonal axes
The vocabulary stays small by separating four independent dimensions:
- Forces — what a body does to nearby matter (attract, swirl, gravity, lens…). They compose. Reference →
- Conditions — when a force acts, via
data-when(active, fast, slow, hot, cool, scrolling). Reference → - Formations — a single global bias applied to every particle at once (ambient, wells, lanes, scatter, accretion). Reference →
- Render modes — the same physics drawn differently (dots, trails, links, metaballs, voronoi, streamlines). Reference →
See the primitives move
Each frame below is a live <field-cell> running one force — the
lightweight demo surface with its own small particle pool, not the page field. Move your
cursor through a frame to push the matter around.
Agents: particles, elements, events
Forces don't only push particles. The same force can target a particle
(the default), a DOM element (it gets moved by a transform offset, with
an anchor tether), or an event (crossing a density threshold fires a
CustomEvent you can listen for). One mechanism, three kinds of target — so the
field can drive real app behaviour, not just pixels.
blackhole, galaxy,
tornado — are presets that expand to several
co-located bodies, one primitive each. The engine never grows to add a preset.