Skip to main content
Expression

Motion

Draft

Tokenised duration and easing values that communicate relationships, provide feedback, and guide attention cohesively.

Common alternative names

Animation, transitions, timing, easing


Principles

Purposeful, not decorative

Every animation must serve a function: confirming an action, revealing content, or directing focus. If an animation does not help the user understand what happened or what to do next, remove it.


Fast by default

Users should never feel like they are waiting for an animation to finish. Most interactions use --motion-duration-fast (100ms) or --motion-duration-normal (200ms). Reserve longer durations for complex transitions that involve spatial rearrangement.


Respect user preferences

Arch UI honours prefers-reduced-motion: reduce at the token level. When reduced motion is active, all duration tokens above instant resolve to 0ms, effectively disabling animations without any code changes from consumers.


Duration

Duration tokens control how long a transition takes. Shorter durations feel snappy and responsive; longer durations give the eye time to follow larger movements.

TokenCSS variableValueUse case
motion.duration.instant--motion-duration-instant0msImmediate state changes with no perceptible transition
motion.duration.fast--motion-duration-fast100msMicro-interactions: button press, checkbox toggle, icon swap
motion.duration.normal--motion-duration-normal200msDefault for most transitions: hover states, colour changes, opacity fades
motion.duration.slow--motion-duration-slow300msMedium transitions: dropdown open, accordion expand, tooltip appear
motion.duration.slower--motion-duration-slower500msComplex transitions: page-level reveals, panel slides, staggered lists

Duration visualised

instant
0ms
fast
100ms
normal
200ms
slow
300ms
slower
500ms

Choosing a duration

Use the smallest duration that still lets the user perceive the change. A good heuristic:

  • Under 100px of movement: --motion-duration-fast
  • 100 to 300px of movement: --motion-duration-normal or --motion-duration-slow
  • Full-screen or multi-element choreography: --motion-duration-slower

Easing

Easing curves define the acceleration profile of an animation. They are the difference between motion that feels mechanical and motion that feels natural.

Primitive easing tokens

TokenCSS variableValueCharacter
motion.easing.linear--motion-easing-linearlinearConstant speed, no acceleration. Use for progress bars and looping animations.
motion.easing.ease-in--motion-easing-ease-incubic-bezier(0.4, 0, 1, 1)Starts slow, accelerates. Elements leaving the viewport.
motion.easing.ease-out--motion-easing-ease-outcubic-bezier(0, 0, 0.2, 1)Starts fast, decelerates. Elements entering the viewport.
motion.easing.ease-in-out--motion-easing-ease-in-outcubic-bezier(0.4, 0, 0.2, 1)Symmetrical acceleration. Default for on-screen state changes.
motion.easing.spring--motion-easing-springcubic-bezier(0.175, 0.885, 0.32, 1.275)Slight overshoot. Playful reveals, toasts, popovers.
motion.easing.bounce--motion-easing-bouncecubic-bezier(0.68, -0.55, 0.265, 1.55)Pronounced overshoot. Attention-grabbing, use sparingly.

Easing curves visualised

linear
Constant speed
ease-in-out
Default
spring
Overshoot

Do and Don't

transition: transform var(--motion-duration-fast) var(--motion-semantic-easing-enter);
✓ Do

Use semantic tokens in components so intent is clear and changes propagate automatically.

transition: transform 150ms cubic-bezier(0.4, 0, 0.2, 1);
✕ Don't

Hardcoding cubic-bezier values or raw millisecond durations bypasses the token system and breaks reduced-motion support.

transition: transform 200ms, opacity 200ms;
✓ Do

Prefer transform and opacity for animations. These properties are GPU-composited and do not trigger layout recalculation.

transition: width 200ms, margin-left 200ms;
✕ Don't

Animating width, height, top, left, or margin triggers expensive layout reflows on every frame.

transition-duration: var(--motion-duration-fast);
✓ Do

Keep exit animations faster than enter animations. Users want departing UI to get out of the way quickly.

transition-duration: var(--motion-duration-slower);
✕ Don't

Using slower durations for simple hover states feels sluggish. A 500ms delay on a button hover is too slow.

@media (prefers-reduced-motion: reduce) { /* no motion */ }
✓ Do

Test with prefers-reduced-motion: reduce enabled. Verify that the interface is still fully usable without any animation.

transform: translateY(-2px); /* only signal */
✕ Don't

Don't rely on animation as the only feedback mechanism. Pair motion with a visible state change so reduced-motion users are not left guessing.


Performance checklist

  1. Animate composited properties only. transform, opacity, and filter are cheap. Everything else triggers layout or paint.
  2. Use will-change sparingly. Add it right before an animation starts and remove it after. Permanent will-change wastes GPU memory.
  3. Avoid animating during scroll. If you must, use IntersectionObserver to trigger animations only when elements enter the viewport.
  4. Cap concurrent animations. More than three simultaneous animated elements on screen can cause frame drops on lower-end devices.

Accessibility requirements

  • All animations must be disabled when prefers-reduced-motion: reduce is active. This is handled automatically by the token system.
  • No animation should flash more than three times per second (WCAG 2.3.1).
  • Content must not require animation to be understood. Animation supplements meaning but never replaces it.
  • Auto-playing animations (loading spinners excluded) should pause after 5 seconds or provide a way to stop them (WCAG 2.2.2).