Examples
Live examples
Every demo here is real, grouped by what it shows. Reciprocity: the field reacts to elements and writes density back. Layout as physics: the same springs and repulsion, applied to where elements sit. Field control: driving the field from your own events. Each comes with its source.
A glowing hero word
The canonical reciprocity effect. The word is an attract body with
data-feedback; its --d drives weight and glow, so it grows where
matter gathers. Sweep your cursor across it.
<span
class="hero"
data-body="attract"
data-strength="1"
data-range="340"
data-feedback
>gravity</span>
<style>
.hero {
--d: 0; /* the field eases this in ∈ [0,1] */
font-weight: 700;
font-variation-settings: 'wght' calc(380 + var(--d) * 420);
color: color-mix(in srgb, #4da3ff calc(40% + var(--d) * 55%), #fff);
text-shadow: 0 0 calc(var(--d) * 44px) rgba(77, 163, 255, var(--d));
}
</style> A reacting card grid
Each card is a gentle attractor. Its --d drives a subtle lift, edge glow, and
shadow — the grid breathes as the field moves through it.
<article class="card" data-body="attract" data-strength="0.6" data-range="220" data-feedback>
…card…
</article>
<style>
.card {
--d: 0;
transform: scale(calc(1 + var(--d) * 0.03));
border-color: color-mix(in srgb, #4da3ff calc(var(--d) * 75%), rgba(255,255,255,0.1));
box-shadow: 0 0 calc(var(--d) * 30px) rgba(77, 163, 255, calc(var(--d) * 0.5));
transition: transform 0.25s, border-color 0.25s, box-shadow 0.25s;
}
</style> Conserved emphasis
One finite attention budget across the page (setAttention).
Focusing one item lifts it and makes the others recede by the same total — emphasis you
can't fake with hover styles, because the page can never over-emphasize more than the budget
allows. Hover or tab through the list, then flip the budget off to feel the difference.
- Designing with forcewhy every element carries weight
- The reciprocal fieldelements bend it; it bends them back
- Conservation as a lawnothing created from nothing
- Reading as attentiona budget you spend by looking
- Layout as physicsposition is an equilibrium, not a value
<ul class="reading-list">
<li data-hot data-feedback data-body="attract" data-strength="1.1" data-range="240">
Designing with force
</li>
<!-- …more items, each its own attractor… -->
</ul>
<script type="module">
const field = document.querySelector('field-root');
field.setAttention(true); // ONE conserved budget across the page:
// focusing one body starves the others
</script>
<style>
.reading-list li {
--d: 0; /* the field eases this in ∈ [0,1] */
opacity: calc(0.4 + var(--d) * 0.6); /* idle recedes, focus rises */
font-weight: calc(400 + var(--d) * 320);
transition: opacity .25s, font-weight .25s;
}
</style> Self-spacing labels
Each chip is a data-move="layout" body: anchored to where it belongs, repelling its
neighbours, drifting off dense field regions. Drop them in a loose pile and they spread to even
spacing and re-settle when disturbed — collision-free placement with no layout code. Click the
stage to shove them.
<div class="tag-cloud">
<span class="tag" data-move="layout">design</span>
<span class="tag" data-move="layout">physics</span>
<span class="tag" data-move="layout">layout</span>
<!-- …more chips, all stacked at the same point… -->
</div>
<style>
.tag-cloud { position: relative; min-height: 260px; }
.tag {
position: absolute; left: 50%; top: 50%; /* all start at one point */
margin: -1.05em 0 0 -3em; /* rough centring */
will-change: transform; /* the engine adds the offset */
}
</style>
<!-- data-move="layout": each chip anchors to home, repels its neighbours,
and drifts off dense field regions. Stacked at one point, the cluster
spreads itself to even spacing and re-settles on disturbance or resize.
No layout code. --> Magnetic snapping
The same spring that powers the tether force, applied to an
element's transform. Drag the tile anywhere — on release it springs to the nearest grid cell.
A damped spring, not a stepped round(), so it carries momentum and settles.
// The tether force, applied to an element's transform instead of a particle:
// v += (target − x) · k; v *= damping; x += v
// Critically damped (k≈0.18, damping≈0.72) → it settles without overshoot.
function springTo(el, tx, ty) {
const s = (el._s ||= { x: 0, y: 0, vx: 0, vy: 0 });
s.tx = tx; s.ty = ty;
const tick = () => {
s.vx = (s.vx + (s.tx - s.x) * 0.18) * 0.72;
s.vy = (s.vy + (s.ty - s.y) * 0.18) * 0.72;
s.x += s.vx; s.y += s.vy;
el.style.transform = `translate(${s.x}px, ${s.y}px)`;
if (Math.hypot(s.tx - s.x, s.ty - s.y) > 0.15) requestAnimationFrame(tick);
};
tick();
} tile.addEventListener('pointerup', () => {
const s = tile._s;
// snap to the nearest grid cell — spring carries it the rest of the way
springTo(tile, Math.round(s.x / GRID) * GRID, Math.round(s.y / GRID) * GRID);
}); Spring reflow
Layout transitions as physics. When the order changes, each chip springs from its old position to its new one — the FLIP technique, but the Play step is the tether spring instead of a CSS curve, so the grid settles instead of snapping. Shuffle it.
// FLIP, but the Play step is a spring instead of a CSS curve.
function reflow(items, mutate) {
const first = items.map((el) => el.getBoundingClientRect());
mutate(); // reorder / filter the DOM
items.forEach((el, i) => {
const last = el.getBoundingClientRect();
el._s.x += first[i].left - last.left; // invert: jump back to the old spot
el._s.y += first[i].top - last.top;
springTo(el, 0, 0); // play: spring to the new spot
});
} Click to burst
Drive the field from your own events. The FieldHandle's
burst takes viewport coordinates — click anywhere in the stage to shove and heat
the matter there.
const field = document.querySelector('field-root');
stage.addEventListener('click', (e) => {
field.burst(e.clientX, e.clientY, '#4da3ff'); // shove + heat at the cursor
});