Authoring across surfaces
Three surfaces, one contract. A body authored in plain HTML, as a web component, or in React is
the same body in the field — each is just a different way to write the
[data-body] contract the scanner reads. Pick the surface your stack already uses;
field-ui does not ask you to adopt a framework.
A body, three ways
Each of these authors one attractor of strength 0.8. They compile to identical bodies.
<!-- the page is the medium; any element can be a body -->
<canvas id="field"></canvas>
<article data-body="attract" data-strength="0.8">
Pulls matter inward
</article>
<script type="module">
import { createField } from 'field-ui';
// mounts the field and scans the page for [data-body] elements
createField(document.getElementById('field'));
</script> <!-- <field-root> is the singleton field; it scans [data-body] for you -->
<script type="module">import '@field-ui/elements';</script>
<field-root accent="#4da3ff"></field-root>
<article data-body="attract" data-strength="0.8">
Pulls matter inward
</article> import { FieldField } from '@field-ui/react';
export function Page() {
return (
<>
<FieldField accent="#4da3ff" />
{/* the same [data-body] contract — the field reacts to it */}
<article data-body="attract" data-strength="0.8">
Pulls matter inward
</article>
</>
);
} attract, strength 0.8 — across all three surfaces.Live
These cards carry real data-body attributes, so the page's field reacts to them right
now. Move your pointer near one and watch the field gather around it.
The shared contract
Every surface writes the same data-* attributes. This is the contract:
| Attribute | Meaning | Example |
|---|---|---|
data-body | force tokens, space-separated | attract, repel swirl |
data-strength | source mass / pull (default 0.5) | 0.8 |
data-range | reach in px (default 280) | 220 |
data-preset | a named bundle of virtual bodies | dipole |
data-intent | authored intent, compiled to forces | gather |
data-field-role | semantic role → a default force | source, sink, anchor |
Beneath the surfaces: the platform
The surfaces above author bodies. When you need the page itself to participate — to
measure elements, hold state, and write feedback on a disciplined loop — reach for
@field-ui/platform directly. It is framework-agnostic; the same code runs under any
surface.
import { createFieldPlatform } from '@field-ui/platform';
// the substrate beneath every surface: measure → state → write, on one scheduler
const platform = createFieldPlatform(document.querySelector('article'));
const card = document.querySelector('[data-body]');
platform.measure.register(card); // read phase
platform.feedback.bind(card, { attention: '--field-attention' }); // write phase
platform.on('compute', () => { // compute phase
const m = platform.measure.for(card);
if (m) platform.state.set(card, 'attention', m.visibilityRatio);
});
const tick = (t) => { platform.tick(t); requestAnimationFrame(tick); };
requestAnimationFrame(tick);