Resources
Troubleshooting
The handful of things that trip people up — each with the why and the fix. Most reduce to one of two facts: the field reads bodies on a schedule, and it refuses to fake motion.
New elements aren't reacting
The field scans for [data-body] elements when it mounts. If you add or remove
bodies afterwards — a route change, a list update, a modal — it won't see them until you
re-scan. Call field.scan() (or its alias rescan()) after the DOM
settles.
// plain DOM / web component
field.scan();
// React — bodies render after the field mounts, so scan from onReady
<FieldField onReady={(field) => field.scan()} /> The field is frozen or blank
Two behaviours pause the loop on purpose — both are features, not bugs:
- Reduced motion. Under
prefers-reduced-motion: reducethe simulation renders a single static frame (dt = 0) and never animates. Test with the OS setting off to see motion. - Backgrounded tab. The loop stops on
visibilitychangeand resumes when the tab is foregrounded again, so a hidden tab costs nothing. - Off-screen cells. A
<field-cell>gates its loop on anIntersectionObserver— it only runs while visible.
The canvas covers my content or eats clicks
With <field-root> and the React component this is handled for you. If
you own the canvas via createField, position it fixed behind your content and
let pointer events pass through:
/* the canvas is decorative — behind content, never intercepting clicks */
.forces-canvas {
position: fixed;
inset: 0;
z-index: 0; /* your content sits above */
pointer-events: none;/* clicks pass through */
} <field-root> elements — for small in-frame effects use
<field-cell> instead, which is built to be embedded many times.
--d isn't updating my element
Density write-back is opt-in. The element needs data-feedback, and your CSS
should default --d so it renders before the first write. The value is low-pass
filtered (~0.2s), so it eases rather than jumps.
<!-- 1. opt the element in -->
<h1 data-body="attract" data-range="320" data-feedback>weighty</h1>
<style>
h1 {
--d: 0; /* the engine eases this in ∈ [0,1]; default it yourself */
font-variation-settings: 'wght' calc(380 + var(--d) * 420);
}
</style> --d drives weight,
glow, and scale on the real element.
SSR & hydration
The field is a client-only concern. The custom element registers in the browser, and the
React component mounts its canvas on the client — there is no server-rendered canvas to
mismatch. In Astro, the import '@field-ui/elements' lives in a client
<script>; in Next.js, render the field in a client component. Bodies are
plain markup, so they server-render fine; only the field's reaction is deferred to the
client.
It feels heavy on a large page
The field is one shared canvas, so cost does not grow with the number of bodies beyond the
per-body loop term. If a very large or low-power surface needs headroom, lower
density — density: 0.5 halves the particle
count. See Performance for the full picture.