Getting started
Your first field
Eight short steps from npm install to a headline that gains weight and glow as
the field pools around it. Every step is runnable on its own — paste, save, watch.
1. Install
Pick the entry point for your stack. The rest of this tutorial uses the web component.
npm i @fundamental-engine/elements npm i @fundamental-engine/react npm i @fundamental-engine/vanilla 2. Drop the field
One field lives behind your whole page. Register the element and drop a single
<field-root> — it mounts a fixed, full-viewport canvas and starts the
engine. Nothing reacts yet; there are no bodies.
<script type="module">
import '@fundamental-engine/elements';
</script>
<!-- one field, behind everything -->
<field-root accent="#4da3ff"></field-root> import { FieldField } from '@fundamental-engine/react';
export default function App() {
return (
<>
<FieldField accent="#4da3ff" />
{/* your app */}
</>
);
} import { mountField } from '@fundamental-engine/vanilla';
// creates + manages a fixed full-viewport canvas, returns the handle
const field = mountField({ accent: '#4da3ff' }); 3. Add your first body
A body is any element with data-body. Mark your headline an
attract body and the field starts pulling matter toward it. Move the element and
the force moves with it — the engine re-reads its box every frame.
<h1 data-body="attract" data-strength="0.9" data-range="320">
Fundamental
</h1> attract on the page field through the data-body attribute. field.scan() once from onReady to pick them up.
// bodies are plain elements anywhere in your tree
<h1 data-body="attract" data-strength="0.9" data-range="320">
Fundamental
</h1>
// React renders bodies after the field mounts — rescan once they're in:
<FieldField accent="#4da3ff" onReady={(field) => field.scan()} /> 4. Make the type react
This is the payoff. Add data-feedback and the field writes the density it
gathered back to your element as --field-density (∈ [0, 1], low-pass filtered so it
eases; the compact --d is written too as an alias). Drive weight and glow from it —
the word grows where matter collects.
<h1 data-body="attract" data-range="320" data-feedback>Fundamental</h1>
<style>
h1 {
/* the engine eases this in, density ∈ [0,1]. --field-density is the primary
variable; --d is the compact compatibility alias (both are written). */
--d: var(--field-density, 0);
font-variation-settings: 'wght' calc(380 + var(--field-density, 0) * 420);
text-shadow: 0 0 calc(var(--field-density, 0) * 36px) rgba(77, 163, 255, var(--field-density, 0));
}
</style> --field-density only enhances it; particles never assemble into letterforms.
prefers-reduced-motion the field freezes and the
element holds a static, emphasised baseline. The point the motion made — which word carries weight
— survives with the motion off. Always give --field-density a reduced-motion fallback.
5. Compose another force
Forces compose, space-separated. Add swirl and your headline now both pulls and
spins the matter around it. Each force reads only the attributes it uses —
swirl takes data-spin; attract ignores it.
<h1
data-body="attract swirl"
data-strength="0.9"
data-range="320"
data-spin="1"
data-feedback
>Fundamental</h1> data-body markup above;
this cell just isolates one token so you can see its motion. 6. Gate it on a condition
A force can act only when a condition holds, via data-when. Mark an
element data-hot so hover and keyboard focus set its engaged state, then gate the
force on active — it swirls only while the link is engaged.
<a data-hot data-body="swirl" data-when="active" data-spin="1" data-range="220">
hover me
</a> 7. Drive it from code
Every entry point hands you a FieldHandle. With the web component it's proxied onto the element, so you can reshape the field and react to events:
const field = document.querySelector('field-root');
// a global bias on every particle
field.setFormation('wells');
// a one-shot shove + heat at the cursor
document.addEventListener('click', (e) => field.burst(e.clientX, e.clientY));