One boundary, ordinary HTML, optional behaviors.
DYNT is not a component library and does not require every card, button, or section to import an engine. Initialize an engine against an explicit root, provide a selector, and matching elements inside that boundary become managed surfaces.
@dynt/formation never imports Kinetic, and @dynt/kinetic never imports Formation. Install only what the interface needs.
| Package | Owns | Use it when |
|---|---|---|
@dynt/formation | Construction geometry, viewport travel, reveal, withdrawal | Surfaces should visibly form and deconstruct |
@dynt/kinetic | Tilt, drift, waves, cell geometry, impact, content response | Surfaces should physically respond to input |
@dynt/react | React mount and cleanup lifecycle | Your boundary is owned by a React component |
@dynt/web-components | Custom-element connection lifecycle | You want a declarative framework-neutral host |
Choose the smallest package set.
Formation only
npm install @dynt/formationKinetic only
npm install @dynt/kineticBoth engines
npm install @dynt/formation @dynt/kineticReact adapter
npm install @dynt/react @dynt/formation
# or @dynt/kineticImport the stylesheet belonging to every installed core engine once at the application entry point or shared layout.
import '@dynt/formation/styles.css';
import '@dynt/kinetic/styles.css';Start with one root and one selector.
The root may be a Document, DocumentFragment, ShadowRoot, or HTML element. The selector is evaluated only inside that root. Matching the root itself is supported.
const root = document.querySelector('#app');
const kinetic = createKinetic({
root,
selector: 'section, article, button, [data-dynt-surface]',
exclude: '.third-party-widget',
observe: true
});data-dynt-ignore or an application-specific exclude selector.Construction is a reversible lifecycle.
Formation can send transient lines from viewport edges, acquire targets in sequence, construct permanent geometry, reveal content, and reverse the same choreography during withdrawal.
import { createFormation } from '@dynt/formation';
const formation = createFormation({
root: document.querySelector('#app'),
selector: '[data-dynt-surface]',
profile: 'line-push',
observe: true,
viewportFlow: {
duration: 1160,
stagger: 110,
lineLength: 680,
overrun: 36
},
tokens: {
duration: 380,
lineColor: '#ffbf00',
lineWidth: '1px',
overflow: 14
}
});
formation.withdraw();
formation.form();Lifecycle phases
Subscribe when application logic needs to observe the phase. Do not use fixed timers to guess when construction completed.
const unsubscribe = formation.subscribe(({ element, phase }) => {
if (phase === 'formed') element.dispatchEvent(new Event('ready'));
});
// Later
unsubscribe();Built-in profiles
line-push, line-rise, arc-trace, squircle-sweep, chamfer-fold, magnetic-segment, radial-compass, aperture-iris, and elastic-membrane all share the same controller contract.
Separate contact response from waves.
Tilt is pointer-position driven. The near side compresses, the opposite side rises, an opposing shadow reinforces depth, and locally owned content can travel through its own bounded channel. Hovering does not create cell geometry.
import { createKinetic } from '@dynt/kinetic';
const tilt = createKinetic({
root,
selector: '[data-tilt-surface]',
effects: {
tilt: true,
content: true,
wave: false,
drift: false
},
motion: {
maxTilt: 1.35,
response: 0.18,
contentTravel: 3
}
});1.35deg is the restrained preset baseline. Higher values make the mechanism easier to teach but should be validated against real content before production use.
Mark a custom content group with data-dynt-reactor. Kinetic moves the group with CSS translate and leaves the host element's application-owned transform untouched.
Render cell geometry only while a wave is active.
A click or programmatic impact creates a circular front from the real input location. Coherent turbulence bends the front while speed, thickness, recovery, intensity, and growth remain independently configurable.
const wave = createKinetic({
root,
selector: '[data-wave-surface]',
effects: {
tilt: false,
content: true,
wave: true,
drift: false
},
cells: {
shape: 'hexagon',
size: [22, 18, 14],
colorMode: 'gradient',
colors: ['#143447', '#1686bd', '#8de1ff']
},
flow: {
speed: 1,
thickness: 1,
recovery: 1.1,
intensity: 1.2,
turbulence: 0.42,
multi: true,
maxWaves: 3,
maxCells: 420
}
});| Geometry | Visual behavior | Useful for |
|---|---|---|
| Square | Regular rectilinear field with configurable gap | Structured dashboards and data surfaces |
| Hexagon | Connected staggered honeycomb | Continuous material-like propagation |
| Circle | Separated radial cells | Making curved wave fronts easy to read |
| Diamond | Connected interlocked tessellation | Emphasizing directional movement |
Three-level cell sizing
The size tuple is [section, card, nested]. Nested managed surfaces inherit the next level, capped at level three. The site demonstrations use the finer [22, 18, 14] hierarchy so cells remain precise inside cards.
// One target can override the resolved setting.
element.dataset.dyntCellShape = 'circle';
element.dataset.dyntCellSize = '18';
// Or use CSS.
element.style.setProperty('--dynt-cell-size', '18px');Compose through shared DOM state, not package imports.
Initialize both engines against the same root and selector. Formation owns construction state. Kinetic observes the Formation phase marker, rests while a target is not formed, and resumes input after formation completes.
const options = {
root: document.querySelector('#app'),
selector: '[data-dynt-surface]',
observe: true
};
const formation = createFormation({
...options,
profile: 'line-push',
viewportFlow: true
});
const kinetic = createKinetic({
...options,
...kineticPresets.structural,
cells: { size: [22, 18, 14] }
});Destroying either controller leaves the other operational. Shared ownership records prevent duplicate decoration and restore application state only after the final owner releases a target.
Scroll loading, route changes, and inserted cards are supported.
Set observe: true when matching elements may appear later. Mutation records are batched. New matches are enhanced, removed elements are restored, and targets that become excluded are released.
const kinetic = createKinetic({
root,
selector: '[data-dynt-surface]',
observe: true
});
// Added later by React, a router, or an IntersectionObserver.
root.append(newSurface);
// Optional explicit reconciliation.
const adopted = kinetic.refresh();As long as the new element enters the observed boundary and matches the selector, the existing controller adopts it.
Thin lifecycle helpers, identical engine behavior.
React
import { useRef } from 'react';
import { useKinetic } from '@dynt/react/kinetic';
export function AppShell() {
const rootRef = useRef<HTMLElement>(null);
useKinetic({
rootRef,
selector: '[data-dynt-surface]',
observe: true,
cells: { size: [22, 18, 14] }
});
return <main ref={rootRef}>{/* existing tree */}</main>;
}Use useFormation from @dynt/react/formation for Formation. Memoize object and array configuration when it should remain stable across renders.
Web Components
import { defineKineticElement } from '@dynt/web-components/kinetic';
defineKineticElement('dynt-kinetic-root', {
selector: '[data-dynt-surface]',
observe: true
});<dynt-kinetic-root>
<article data-dynt-surface>Existing semantic content</article>
</dynt-kinetic-root>Use CSS channels for dark and light surfaces.
Application themes can provide local line, fill, cell, and surface colors. Keep contrast appropriate for both the surrounding UI and transient geometry.
:root {
--dynt-line-color: #ffbf00;
--dynt-kinetic-color: #64c6ff;
}
[data-theme='light'] [data-dynt-surface] {
--dynt-line-color: #8f5d00;
--dynt-kinetic-color: #0076a8;
}Controller tokens, selector groups, and target-local data attributes can override the global theme. Target-local configuration has final priority for supported channels.
Pause, update, reconcile, and destroy deliberately.
| Controller action | Purpose |
|---|---|
refresh() | Reconcile current selector matches and return the number newly enhanced |
update(...) | Change supported configuration without rebuilding managed elements |
pause() / resume() | Suspend Kinetic input and return surfaces to rest, then resume |
impact(target, input) | Start a bounded Kinetic response at normalized coordinates |
form() / withdraw() | Run Formation forward or in reverse for one target or the full set |
destroy() | Remove owned listeners, observers, layers, timers, frames, styles, and markers |
// Route or application boundary teardown.
kinetic.destroy();
formation.destroy();destroy() is idempotent. Application-owned classes, attributes, inline properties, and priorities are restored exactly after the final controller releases each target.
Motion remains decoration; semantics stay application-owned.
- Decoration layers are hidden from assistive technology and never receive pointer events.
- Links, buttons, inputs, names, values, focus order, and keyboard behavior remain native.
prefers-reduced-motion: reduceskips Formation travel and removes Kinetic tilt, drift, and waves while preserving meaningful state.- Kinetic uses one controller scheduler, caps active surfaces, limits wave cells, and performs no continuous animation work while idle.
- Do not use motion or line color as the only indication of required information.
Read the complete accessibility contract and performance budgets.
Check the boundary before tuning the effect.
| Symptom | Check |
|---|---|
| A late-rendered section is not enhanced | Confirm observe: true, the element is inside root, and it matches selector |
| A nested card reacts with its parent | Make the nested card a managed surface; deepest matching ownership receives input |
| Cells look too large inside cards | Use a three-level size such as [22, 18, 14] or a target-local size override |
| Tilt is difficult to notice | Verify effects.tilt, use content response, and test at a temporary demonstration value before selecting a restrained production value |
| Wave appears on hover | Hover should leave the canvas clear; inspect application CSS or confirm the active package version |
| A third-party widget is decorated | Add data-dynt-ignore to its boundary or include it in exclude |
| Motion persists after unmount | Ensure the owning boundary calls destroy() or uses the framework lifecycle adapter |
Continue to the complete API reference, composition guide, and repository troubleshooting guide.
