@zero.sc/motion
v0.2.0
Published
Physics-modelled motion for React with zero runtime dependencies — refraction, Fresnel specular, volume-preserving soft bodies, Verlet cloth, ferrofluid, metaballs, rack focus, caustics and velocity-preserving handoff. Reduced-motion respected everywhere.
Maintainers
Readme
Why springs
A CSS easing curve finishes when its clock runs out. If the user changes their mind halfway, the curve keeps its own schedule anyway. A spring carries velocity instead of a duration, so when the target moves it continues from wherever it currently is. That is where "follows your hand" comes from.
Those curves are what this package's own solver produced — not hand-drawn. springStep
was run at 8 ms intervals to sample the coordinates, and bouncy's peak of 1.15 is a
computed result.
Install
npm install @zero.sc/motion
# pnpm add @zero.sc/motion · yarn add @zero.sc/motionreact and react-dom 19 are peers. Nothing else comes along.
Sixty seconds
import { Reveal, Stagger, usePresence } from '@zero.sc/motion';
// 1 — appear once, when it enters the viewport
export function Hero() {
return (
<Reveal>
<h1>Zero</h1>
</Reveal>
);
}
// 2 — lists arrive in sequence; the gap token carries its own ceiling
export function List({ items }: { items: string[] }) {
return (
<Stagger>
{items.map((it) => (
<li key={it}>{it}</li>
))}
</Stagger>
);
}
// 3 — stay mounted until the exit animation has actually finished
export function Sheet({ open }: { open: boolean }) {
const { mounted, state, ref } = usePresence(open);
if (!mounted) return null;
return (
<div ref={ref} data-state={state}>
sheet
</div>
);
}Five spring tokens
Pick a situation, not a number. These curves are integrated results too.
| Token | When | Character |
|-------|------|-----------|
| snappy | Cursors and drags — anything that must track a hand | Settles first |
| default | Most things | Firm, no overshoot |
| gentle | Sheets and panels — large surfaces | Unhurried |
| bouncy | Small elements saying "I'm alive" | Overshoots to 1.15, then sits down |
| heavy | Modal scrims — things that should carry inertia | Weighted |
import { useSpringStyle } from '@zero.sc/motion';
const ref = useSpringStyle<HTMLDivElement>(isOpen ? 1 : 0, {
config: 'snappy',
render: (value, node) => {
node.style.transform = `scale(${value})`;
},
});render touches the DOM directly, inside rAF. It does not re-render on every value
change — a number running at 60 fps that shakes the React tree eats the frame budget
it was trying to protect.
What's inside
The spring solver · Presence (stays through exit) · Reveal (viewport entry) ·
Stagger (sequence) · useFlip (FLIP position transitions) · a View Transitions wrapper ·
pointer reactions like Magnetic, Tilt and Ripple · text effects like SplitText,
Typewriter and CountUp — 131 exports.
Reduced motion is not an option flag
For a user who has turned on prefers-reduced-motion: reduce, every translation, scale
and rotation is switched off. Not everything disappears — opacity transitions remain,
because a state change still has to be visible. That decision lives in one place,
motion-mode, rather than in each component, and an app can force it with
setMotionOverride.
import { useReducedMotion, useShouldAnimate } from '@zero.sc/motion';
const reduced = useReducedMotion(); // the system setting
const animate = useShouldAnimate(); // system + app override, resolvedHonest limits
- Layout animation stops at FLIP. There is no automatic layout tracking — you tell
useFlipwhen to measure. - No SVG path morphing.
Morphtransitions between rectangles; it does not interpolate paths. - This is 0.x. Semver makes no promises here, and the export surface may shrink before 1.0.
- The license text shipped with this version is still an unreviewed draft — see below.
Around it
| | |
|---|---|
| Docs and playground | kit.zero.sc |
| Components | @zero.sc/ui — built on this |
| Everything | @zero.sc |
License
MIT OR Zero License v1.0 — take whichever you prefer. Choosing MIT is enough; nothing further is required of you.
The Zero name, marks and logos are not covered — build anything you like with this code, just don't present it as a Zero product.
Copyright (c) 2026 Zero. Source Code begins at Zero.
