name: crafting-ui-animations description: Designs and implements purposeful UI motion — micro-interactions, page-load and scroll-reveal sequences, transitions, and keyframe or Web Animations API animations that stay smooth and respect reduced-motion preferences. Use when the user asks to add an animation, transition, hover or press effect, loading indicator, scroll reveal, or page-load sequence, or to fix janky or excessive motion. Do not use for accessibility audits (ensuring-accessibility) or loading-performance work (optimizing-web-performance).
Crafting UI Animations
When to use / when NOT to use
- Use for: adding or refining motion — micro-interactions, enter/exit transitions, scroll reveals, load sequences, loading states — and fixing jank or motion overload.
- Do NOT use for: WCAG audits (ensuring-accessibility), bundle/loading speed (optimizing-web-performance), or pure layout work.
Core rules
-
Motion serves purpose. Each animation must communicate state, direct attention, or express identity — one orchestrated moment beats scattered effects. If you can't name its purpose, delete it.
-
Animate only
transformandopacity(compositor-friendly); never animatewidth,height,top,left, ormargin.- ✅
transform: translateY(8px) → none - ❌
top: 8px → 0(forces layout every frame)
- ✅
-
Never move layout after load — reserve space; animate overlays and transforms so CLS stays ≤0.1.
- ✅ banner slides over content, or space is pre-reserved
- ❌ banner insertion pushes the page down
-
Duration/easing tokens: UI feedback 150–300ms; enter
ease-out, exitease-in; larger scene changes ≤500ms. Define once, reuse everywhere. -
Choose the lightest tool that works: two states → CSS
transition; multi-step/looping → CSS@keyframes; runtime-computed values, sequencing, or interruption → Web Animations API. No animation library for what these three cover. -
Always honor
prefers-reduced-motion— gate every non-essential animation, swap movement for opacity or nothing. -
Hierarchy of restraint: micro-interactions everywhere are fine (≤200ms, subtle); attention-seeking motion (bounce, pulse) at most one element per view.
Workflow
- Name the purpose of each requested animation (state feedback / attention / identity). Cut anything purposeless.
- Define or reuse motion tokens (durations, easings) as CSS custom properties.
- Implement with the lightest tool per rule 5, animating only transform/opacity.
- Add the
prefers-reduced-motionfallback for every animation you wrote. - Validate: trigger each animation — no layout shift (DevTools → Performance → Layout Shift regions), steady 60fps (no long purple layout bars), interruption behaves (rapid hover on/off doesn't stutter).
- Reduced-motion check: enable "Emulate CSS prefers-reduced-motion" in DevTools Rendering panel and re-run the flow — nothing essential may be lost.
Edge cases & failure modes
- Animating
display: none→ visible:displaycan't transition; use@starting-style+transition-behavior: allow-discrete, or WAAPI with a visibility swap. - Scroll reveals below the fold: use
IntersectionObserver, reveal once, and ensure content is visible without JS (progressive enhancement — never leaveopacity: 0as the no-JS state). - Infinite/looping animation (spinners excepted) → must pause when not visible (
animation-play-state, or stop the WAAPI animation) and under reduced motion. - Jank persists despite transform/opacity → check for unintentionally huge paint areas; promote with
will-change: transformsparingly and remove it after the animation. - Autoplaying motion >5s must have a pause control (WCAG 2.2.2) — flag this instead of shipping it silently.
References
Copy-paste patterns (motion tokens, enter/exit, scroll reveal, WAAPI sequencing, reduced-motion): see references/patterns.md