Motion is a supporting layer, not a feature. It confirms input, smooths state change, and directs attention — then gets out of the way. Three easings, three durations, and a short list of patterns cover 95% of product motion in Akku.
How to decide whether a thing moves, and how.
Every transition answers a question: "did that land?", "where did it go?", "what changed?". If you can't name the question, skip the motion.
Most UI motion lives between 120ms and 280ms. Anything under feels glitchy; anything over feels laggy. Reserve longer durations for large spatial moves.
All motion defers to prefers-reduced-motion. Transitions become instant; meaning is carried by state, not choreography.
Three curves for three jobs. Click any card to replay.
Decelerates into rest. The default for entrances, expansions, and anything appearing.
cubic-bezier(0.22, 1, 0.36, 1)Symmetric. Used when an element moves between two named positions — tabs, drawers, page transitions.
cubic-bezier(0.65, 0, 0.35, 1)Accelerates out. Reserved for exits — items dismissed, sheets closing, content leaving view.
cubic-bezier(0.4, 0, 1, 1)Three core tokens, plus two extended for choreography. Pick by the distance traveled and the importance of the event.
Composed recipes. Each uses only the easings and durations above — no bespoke timing.
Default entrance for most mounted content — list items, inline reveals, expanded panels.
For overlays and popovers: starts 92% scale and fades up. Origin is the trigger.
Used for sheets, drawers, and nav items. Direction should match the anchor of the element.
Confirmation motion. In with ease-out, holds 2s, out with ease-in. Never bounces.
Interactive surfaces rise by 4px on hover. Shadow deepens one step. Try hovering the card.
Focus is a motion moment. Border snaps to iris-500 and a 4px iris-100 ring blooms.
Loading placeholder. Linear gradient sweeps at constant speed — no acceleration.
When the user prefers less motion, transitions become instant (10ms) and looping animations stop. Meaning is preserved through state, not timing.
Copy-paste into any project using _shared.css.
/* Easings */
--ease-out: cubic-bezier(0.22, 1, 0.36, 1);
--ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
--ease-in: cubic-bezier(0.4, 0, 1, 1);
/* Durations */
--dur-instant: 80ms;
--dur-fast: 120ms;
--dur-base: 180ms;
--dur-slow: 280ms;
--dur-slower: 440ms;
/* Reduced motion guard */
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}