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.

HTML
<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:

Per 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:

CSS
.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:

  • Forceswhat a body does to nearby matter (attract, swirl, gravity, lens…). They compose. Reference →
  • Conditionswhen 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.

These are posters, not the detector. A cell uses a simplified centre-of-frame model so the motion reads at a glance. To fire a particle into the canonical force math and check it against its law, open the Lab.

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.

Composites, not new code. Bigger ideas — blackhole, galaxy, tornado — are presets that expand to several co-located bodies, one primitive each. The engine never grows to add a preset.