Skip to main content
Technology & EngineeringDesign Systems217 lines

Motion Guidelines

Motion and animation guidelines, easing curves, and transition patterns for design systems

Quick Summary15 lines
You are an expert in motion and animation guidelines for building and maintaining design systems.

## Key Points

1. **Purposeful** — every animation should answer "what is this helping the user understand?" If there is no answer, remove it.
2. **Quick** — UI transitions should feel instantaneous to snappy; most component animations fall between 100ms and 300ms.
3. **Natural** — use easing curves that mimic real-world physics; linear motion feels robotic.
4. **Accessible** — respect `prefers-reduced-motion` and never rely on animation as the sole means of conveying information.
- Always wrap motion in `prefers-reduced-motion` checks — disable or drastically shorten animations for users who have enabled reduced motion at the OS level.
- Use the duration scale tokens consistently; do not hard-code arbitrary durations in component CSS.
- Pair enter and exit animations so components do not appear instantly but disappear with a transition, or vice versa.
- Using `transition: all` which animates unintended properties (like width or height), causing janky layout recalculations and poor performance.
- Making animations too slow in an effort to be "polished" — anything over 300ms for a simple component transition feels sluggish and hurts perceived performance.
skilldb get design-systems-skills/Motion GuidelinesFull skill: 217 lines
Paste into your CLAUDE.md or agent config

Motion Guidelines — Design Systems

You are an expert in motion and animation guidelines for building and maintaining design systems.

Overview

Motion in a design system serves functional purposes — it communicates state changes, guides attention, establishes spatial relationships, and provides feedback. A motion guideline defines shared duration scales, easing curves, and reusable transition patterns so animations feel cohesive across every component and page.

Core Philosophy

Motion in a design system is functional communication, not decoration. Every animation should answer a specific question: "What just changed?" (a toast appearing), "Where did this come from?" (a modal sliding in from the element that triggered it), or "What should I look at?" (a pulsing notification badge). If an animation does not serve one of these purposes, it is visual noise that slows users down.

Speed is the most important property of UI animation. Research consistently shows that transitions between 100ms and 300ms feel responsive, while anything above 400ms feels sluggish. The design system should encode these durations as tokens so teams do not independently decide that their dropdown should take 600ms. A shared duration scale creates temporal consistency — interactions across the entire product feel like they have the same rhythm and pace.

Accessibility is a hard requirement, not a nice-to-have. Users who enable prefers-reduced-motion at the OS level are communicating a medical need, not a preference. The system must respect this by disabling or drastically simplifying all motion when that media query matches. This means motion tokens should resolve to 0ms under reduced motion, and components should remain fully functional without any animation.

Core Concepts

Motion Principles

  1. Purposeful — every animation should answer "what is this helping the user understand?" If there is no answer, remove it.
  2. Quick — UI transitions should feel instantaneous to snappy; most component animations fall between 100ms and 300ms.
  3. Natural — use easing curves that mimic real-world physics; linear motion feels robotic.
  4. Accessible — respect prefers-reduced-motion and never rely on animation as the sole means of conveying information.

Duration Scale

TokenValueUse case
duration-instant100msMicro-interactions: checkbox, toggle
duration-fast150msTooltips, hover states, ripples
duration-normal250msModals, dropdowns, accordions
duration-slow400msPage transitions, complex reveals
duration-slower600msOrchestrated sequences, onboarding

Easing Curves

TokenValueUse case
ease-defaultcubic-bezier(0.2, 0, 0, 1)General-purpose transitions
ease-incubic-bezier(0.4, 0, 1, 1)Elements leaving the screen
ease-outcubic-bezier(0, 0, 0.2, 1)Elements entering the screen
ease-in-outcubic-bezier(0.4, 0, 0.2, 1)Elements moving on-screen
ease-springcubic-bezier(0.175, 0.885, 0.32, 1.275)Playful bounces

Implementation Patterns

Motion Tokens as CSS Custom Properties

:root {
  --duration-instant: 100ms;
  --duration-fast: 150ms;
  --duration-normal: 250ms;
  --duration-slow: 400ms;

  --ease-default: cubic-bezier(0.2, 0, 0, 1);
  --ease-in: cubic-bezier(0.4, 0, 1, 1);
  --ease-out: cubic-bezier(0, 0, 0.2, 1);
  --ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
}

/* Reduced motion override */
@media (prefers-reduced-motion: reduce) {
  :root {
    --duration-instant: 0ms;
    --duration-fast: 0ms;
    --duration-normal: 0ms;
    --duration-slow: 0ms;
  }
}

Reusable Transition Classes

.transition-fade {
  transition: opacity var(--duration-normal) var(--ease-default);
}

.transition-slide-up {
  transition:
    opacity var(--duration-normal) var(--ease-out),
    transform var(--duration-normal) var(--ease-out);
}

.transition-slide-up[data-state="open"] {
  opacity: 1;
  transform: translateY(0);
}

.transition-slide-up[data-state="closed"] {
  opacity: 0;
  transform: translateY(8px);
}

.transition-scale {
  transition:
    opacity var(--duration-fast) var(--ease-out),
    transform var(--duration-fast) var(--ease-out);
  transform-origin: var(--origin, center);
}

.transition-scale[data-state="open"] {
  opacity: 1;
  transform: scale(1);
}

.transition-scale[data-state="closed"] {
  opacity: 0;
  transform: scale(0.95);
}

Animated Presence with Exit Animations

import { useRef, useState, useEffect } from 'react';

interface AnimatePresenceProps {
  show: boolean;
  children: React.ReactNode;
  className?: string;
  duration?: number;
}

export function AnimatePresence({
  show,
  children,
  className = 'transition-fade',
  duration = 250,
}: AnimatePresenceProps) {
  const [mounted, setMounted] = useState(show);
  const [state, setState] = useState<'open' | 'closed'>(show ? 'open' : 'closed');

  useEffect(() => {
    if (show) {
      setMounted(true);
      // Wait one frame for the element to mount in the "closed" state
      requestAnimationFrame(() => setState('open'));
    } else {
      setState('closed');
      const timer = setTimeout(() => setMounted(false), duration);
      return () => clearTimeout(timer);
    }
  }, [show, duration]);

  if (!mounted) return null;

  return (
    <div className={className} data-state={state}>
      {children}
    </div>
  );
}

Staggered List Animation

.stagger-list > * {
  opacity: 0;
  transform: translateY(8px);
  animation: stagger-in var(--duration-normal) var(--ease-out) forwards;
}

.stagger-list > *:nth-child(1) { animation-delay: 0ms; }
.stagger-list > *:nth-child(2) { animation-delay: 50ms; }
.stagger-list > *:nth-child(3) { animation-delay: 100ms; }
.stagger-list > *:nth-child(4) { animation-delay: 150ms; }
.stagger-list > *:nth-child(5) { animation-delay: 200ms; }

@keyframes stagger-in {
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

@media (prefers-reduced-motion: reduce) {
  .stagger-list > * {
    animation: none;
    opacity: 1;
    transform: none;
  }
}

Best Practices

  • Always wrap motion in prefers-reduced-motion checks — disable or drastically shorten animations for users who have enabled reduced motion at the OS level.
  • Use the duration scale tokens consistently; do not hard-code arbitrary durations in component CSS.
  • Pair enter and exit animations so components do not appear instantly but disappear with a transition, or vice versa.

Common Pitfalls

  • Using transition: all which animates unintended properties (like width or height), causing janky layout recalculations and poor performance.
  • Making animations too slow in an effort to be "polished" — anything over 300ms for a simple component transition feels sluggish and hurts perceived performance.

Anti-Patterns

  • Animation for its own sake. Adding bounce effects, elastic easings, and multi-step transitions to basic UI elements like buttons or checkboxes because they look fun in isolation. In a production interface used hundreds of times a day, clever animations become irritating.

  • Inconsistent timing across components. A dropdown that opens in 150ms, a modal that fades in over 500ms, and a tooltip that appears in 50ms create a disjointed experience. Use the shared duration tokens consistently so all similar interactions feel like they belong to the same product.

  • Enter animations without exit animations. A modal that fades in smoothly but disappears instantly, or a drawer that slides in from the right but vanishes without sliding back, feels half-finished. Always pair entrance and exit animations for a complete interaction loop.

  • Ignoring prefers-reduced-motion. Shipping animations without checking this media query is an accessibility violation. Users with vestibular disorders, seizure conditions, or motion sensitivity rely on this setting, and design systems are in the best position to enforce compliance.

  • Animating layout properties. Transitioning width, height, top, left, or margin triggers expensive browser layout recalculations on every frame. Stick to transform and opacity, which are GPU-composited and do not affect the document flow.

Install this skill directly: skilldb add design-systems-skills

Get CLI access →