Motion
A fixed set of durations, curves and springs that every animation is built from. A generator and a drift test keep the JS and CSS values identical, and ReducedMotionProvider collapses every animation under a reduced-motion preference.
Durations
Five speeds, each covering the same distance. Quick for feedback and exits, normal for menus and dialogs, moderate for large surfaces, slow for ambient motion.
Easings
The dotted diagonal is linear; the solid curve is the token's acceleration. The dot rides it at the slow duration.
Entering and leaving, and when unsure
Moving between two visible positions
Panels sliding in from an edge
Springs
Three springs released at the same moment. Each row shows how fast it arrives and how far it overshoots.
the default for interface motion
indicators and quick follow-ups
where a drag or flick throws something
Scales
Press to shrink, hover to lift. Each chip moves on its token.
Presets
Each preset is a named duration and easing.
Arriving and leaving
- presets.enter
- normal × out
- presets.exit
- quick × out
Feedback
- presets.shake
- normal × out + shakeKeyframes
Rules
Components animate with the m.* LazyMotion primitives, never the full motion.* namespace, so the animation engine is never forced into a consumer's bundle.
packages/ui/oxlint.config.tsReducedMotionProvider wraps every app root. Its MotionConfig reducedMotion="user" collapses every animation under a reduced-motion preference, so no component re-checks the media query.
ReducedMotionProvider (MotionConfig reducedMotion="user")Tokens only, no bespoke curves or durations. The CSS custom properties are generated from motion.ts, and a vitest drift test fails the build when the two diverge.
generate-motion-css.mjs + motion-css-sync.test.tsOne orchestrated moment per page: a single staggered entrance. No scroll-triggered animation, no parallax.
conventionIn practice
Pick the token by what the motion is for.
// packages/ui/src/components/sidebar/sidebar-motion-config.ts
// Opening and closing are ordinary interface motion.
export const SIDEBAR_TRANSITION = springs.standard;
// A drag throws the panel, so the snap earns a small overshoot.
export const SIDEBAR_MAGNETIC_TRANSITION = springs.settle;The tabs indicator, the segmented control and the sidebar show it in use.