Motion
Motion should feel like a dancer’s bounce — quick, springy, never slow or floaty. It confirms what happened; it never makes you wait.
Principles
Feedback first
Animate the result of an action — a press, a save, a selection. Don’t animate to decorate.
One showpiece per screen
Ambient loops (breathing, shimmer) belong to the single main CTA. Everything else stays still at rest.
Respect reduced motion
Every loop and large movement switches off under prefers-reduced-motion.
The brand CTA
Hover, press and watch: breathing gradient, shimmer sweep, springy lift, press-down and a click ripple — all from one class. Its timings use the tokens below.
<button class="wd-cta inline-flex items-center gap-2 px-6 py-3 rounded-full text-white text-sm font-bold uppercase tracking-wider">
Find your next festival
</button>Durations
Four steps. Anything longer than slow is either an ambient loop (only on the brand CTA) or a bug.
80msduration-wd-instantvar(--wd-duration-instant)Press feedback (active state)150msduration-wd-quickvar(--wd-duration-quick)Colour and border changes on hover. Same as Tailwind’s default transition duration.220msduration-wd-standardvar(--wd-duration-standard)Lift, scale, small movements380msduration-wd-slowvar(--wd-duration-slow)Ripples, entrances, panels openingAmbient loops on the brand CTA run 4.5–6s and the gradient shift 800ms; they stay literal inside .wd-cta because nothing else should use them.
Easing
ease-wd-springvar(--wd-ease-spring) · cubic-bezier(0.34, 1.56, 0.64, 1)Playful overshoot — lifts and popsease-wd-outvar(--wd-ease-out) · ease-outThings arriving or settling. The CSS keyword, not Tailwind’s ease-out curve.ease-wd-in-outvar(--wd-ease-in-out) · ease-in-outLoops that go back and forthSee them side by side
Each dot travels the same distance with a different duration and the spring easing.
Using the tokens
<!-- Tailwind -->
<button class="transition-transform duration-wd-standard ease-wd-spring hover:-translate-y-0.5">…</button>
<a class="transition-colors duration-wd-quick hover:text-primary">…</a>
/* CSS */
.thing {
transition: transform var(--wd-duration-standard) var(--wd-ease-spring);
}
.thing:active { transition-duration: var(--wd-duration-instant); }Reduced motion
Required for every looping or large animation.
@media (prefers-reduced-motion: reduce) {
.wd-cta,
.wd-cta::before {
animation: none !important;
}
}
<!-- Tailwind -->
<div class="transition-transform duration-wd-standard motion-reduce:transition-none">…</div>