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

Transactions
3,041,892

useCountUp · 600ms ease-out

Gate 4
Pavilion
Rides

stagger 50ms · 280ms ease-out

Live

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.

enterGeneric show. Opacity + a 4px lift. base + out.
exitGeneric hide. Opacity only. fast + in.
fadeOpacity only. The reduced-motion-safe baseline.
slideOpacity + an 8px translate. base in, fast out.
popOpacity + scale 0.96 to 1. Snappy spring in, fast fade out.
staggerA container that cascades children by the 50ms stagger step.

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.

overview.tsxtsx
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.

list.tsxtsx
/* 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.