Motion System · v1

Crisp.
Purposeful.
Fast.

Four primitives. One mental model. Ships near-zero JS until each island enters the viewport.

motion 12 · Astro 6 islands · WCAG AA
Scroll
"Motion without discipline is noise.
Motion with discipline is product polish."

Most teams reach for a full animation library, ship 80 kb of JS, and get janky layout animations in return. This system is different — one dependency, four thin wrappers, no layout thrashing.

4 primitives
~30 lines each
0 extra deps
3 layers
AA reduced motion

Architecture

Three layers, top to bottom

Start with Primitives — they cover 95% of the site.
Tokens and reduced-motion handling come for free.

1
Primitives

Reveal · Stagger · Parallax · ScrollScene

Drop into .astro files as client:visible islands.

2
Tokens

ease · duration · distance

One source of truth. Change a number, everything feels the same.

3
Reduced motion

usePrefersReducedMotion()

Every primitive consults this. Choreography survives, movement doesn't.

Capabilities

Every primitive, live

Click a tab. Four primitives, plus the physics and tokens that tune them — six demos, one tab bar.

ScrollScene in the wild

Scroll to advance the signal lifecycle

Real scroll, not a loop — the sticky pin below exposes progress 0→1 straight from useScroll, the same primitive the "ScrollScene" tab above only simulates on a timer.

Scroll to advance

Signal entersactive
Route resolves
Handler fires
Loop closes

Performance

The guardrails

Built-in constraints so you can't accidentally ship slow animations.

transform + opacity only

Never top/left/width/height. Composited by the GPU.

will-change: transform on parallax only

Applied per-element, not globally. Never promoted layers you don't need.

contain: layout paint on ScrollScene

Prevents sticky sections triggering layout work elsewhere on the page.

client:visible deferred hydration

Islands don't hydrate until they enter the viewport. First paint is pure HTML.

Tree-shaking

Motion ships ESM. Unused primitives never reach the bundle.

Integration

Three steps

1

Install

bun add motion
2

Import a primitive

import Reveal from '@/components/motion/Reveal.astro'
3

Drop it in your Astro file

<Reveal><HeroHeadline /></Reveal>

That's it. No provider setup, no global config. The motion tokens live in src/lib/motion.ts — edit the four numbers and the whole site recalibrates.

One system.
Zero overhead.

Unused primitives tree-shake to nothing.
Islands defer until they're needed.

motion 12 Astro 6 React 19 client:visible 4 primitives WCAG AA