Design System
Foundations · Motion

Motion.

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.

Principles

How to decide whether a thing moves, and how.

01

Purposeful

Every transition answers a question: "did that land?", "where did it go?", "what changed?". If you can't name the question, skip the motion.

02

Quick, not rushed

Most UI motion lives between 120ms and 280ms. Anything under feels glitchy; anything over feels laggy. Reserve longer durations for large spatial moves.

03

Respect the user

All motion defers to prefers-reduced-motion. Transitions become instant; meaning is carried by state, not choreography.

Easing curves

Three curves for three jobs. Click any card to replay.

ease-out

--ease-out

Decelerates into rest. The default for entrances, expansions, and anything appearing.

cubic-bezier(0.22, 1, 0.36, 1)

ease-in-out

--ease-in-out

Symmetric. Used when an element moves between two named positions — tabs, drawers, page transitions.

cubic-bezier(0.65, 0, 0.35, 1)

ease-in

(dismiss)

Accelerates out. Reserved for exits — items dismissed, sheets closing, content leaving view.

cubic-bezier(0.4, 0, 1, 1)

Duration scale

Three core tokens, plus two extended for choreography. Pick by the distance traveled and the importance of the event.

--dur-instant80ms
80ms
State echoes — hover tint, ripple, checkbox flip. User barely notices; they just feel response.
--dur-fast120ms
120ms
Micro-transitions — focus ring, color shift, small opacity. The default for interactive feedback.
--dur-base180ms
180ms
Standard UI transitions — buttons, tooltips, chips, inline reveals. When in doubt, use this one.
--dur-slow280ms
280ms
Larger shapes moving — cards scaling, panels expanding, tab indicators, menu open.
--dur-slower440ms
440ms
Page-level motion — sheet entrance, modal open, onboarding choreography. Use sparingly.

Patterns

Composed recipes. Each uses only the easings and durations above — no bespoke timing.

Fade & rise

Default entrance for most mounted content — list items, inline reveals, expanded panels.

opacity 0 → 1translateY 8px → 0180ms · ease-out

Scale in

For overlays and popovers: starts 92% scale and fades up. Origin is the trigger.

scale .92 → 1opacity 0 → 1280ms · ease-out

Slide in from side

Used for sheets, drawers, and nav items. Direction should match the anchor of the element.

translateX −30 → 0280ms · ease-out

Toast in & out

Confirmation motion. In with ease-out, holds 2s, out with ease-in. Never bounces.

Workspace created
translateY 80 → 0 → 80400ms in · 400ms out

Hover lift

Interactive surfaces rise by 4px on hover. Shadow deepens one step. Try hovering the card.

translateY 0 → −4pxshadow-sm → shadow-lg180ms · ease-out

Focus ring

Focus is a motion moment. Border snaps to iris-500 and a 4px iris-100 ring blooms.

border + ring120ms · ease-out

Skeleton shimmer

Loading placeholder. Linear gradient sweeps at constant speed — no acceleration.

bg-position 200% → −200%1.8s · linear · infinite

Reduced motion

When the user prefers less motion, transitions become instant (10ms) and looping animations stop. Meaning is preserved through state, not timing.

@media (prefers-reduced-motion: reduce) animation: none · transition: 0.01ms
always ship this guard

Tokens

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;
  }
}