Motion
Movement with restraint
Every primitive draws its enter and exit from the same duration and easing tokens. The result is calm, consistent motion that never gets in the way on a busy gate.
Live
These are the real animations, not screenshots. Replay to watch them again.
Hover lift
Hover to raise 2px on the soft hover shadow.
duration-base · ease-standard
useCountUp · 600ms ease-out
stagger 50ms · 280ms ease-out
dot pulse · ambient loop
Presets
Six canonical presets cover almost every case. Each is a motion-library variant object with initial, animate, and exit states.
Consuming the motion source
The @ludo/motion item is plain data. No DOM, no React, no React Native imports, so web and native read the same tokens. Add it with npx shadcn@latest add @ludo/motion.
In JavaScript
Read durations and easings, or hand a preset straight to the motion library.
import { DURATIONS, EASINGS, PRESETS } from "@/lib/motion";
// Plain data: read tokens directly.
const fast = DURATIONS.fast; // 0.15 (seconds)
const outCurve = EASINGS.out; // [0.16, 1, 0.3, 1]
// Or hand a preset to the motion library as variants.
<motion.div
variants={PRESETS.enter}
initial="initial"
animate="animate"
exit="exit"
/>;In CSS
Most motion needs no library at all. The durations and easings are Tailwind utilities, and staggered entrances work through a single class.
/* Durations and easings are Tailwind utilities from the theme. */
<div className="transition-colors duration-fast ease-standard" />
/* Staggered children, no library needed. */
<ul className="stagger-children">
<li>Gate 4</li>
<li>Pavilion</li>
<li>Rides</li>
</ul>Reduced motion
The policy is tone down, do not kill. The theme ships a global prefers-reduced-motion block that collapses durations to roughly 1ms and neutralises transforms, while opacity and colour still change. Ambient loops pause. You do not wire anything up.
/* Ships in the theme. No setup required. */
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
}
/* Transforms are neutralised; opacity and colour still change. */
}For the motion library, wrap the app root in <MotionConfig reducedMotion="user">. For React Native, check AccessibilityInfo.isReduceMotionEnabled() and pass ReduceMotion.System to springs.