Best Practices

AI Rules for Animation Patterns

AI animates everything with JavaScript and ignores prefers-reduced-motion. Rules for CSS transitions, GPU-accelerated transforms, Framer Motion patterns, reduced motion respect, and animation performance.

7 min read·February 12, 2025

setInterval moving elements pixel by pixel, animating width and height, 15fps jank

CSS transitions, GPU transforms, Framer Motion, reduced motion respect, 60fps performance

AI Animates Like It Is jQuery in 2012

AI generates animations with: JavaScript-driven style changes (setInterval moving elements pixel by pixel), layout-triggering properties (animating width, height, top, left instead of transform), no GPU acceleration (animations run on the CPU main thread, blocking interactions), no motion preference respect (vestibular disorder users get nauseous), and excessive animation (everything bounces, slides, and fades simultaneously). The result: janky 15fps animations that block user input and harm accessibility.

Modern animation is: CSS-first (transitions and keyframes for simple animations), transform-based (translate, scale, rotate, opacity — GPU-composited, no layout recalculation), library-assisted (Framer Motion for complex sequences and gestures), motion-respectful (prefers-reduced-motion disables or reduces animation), and performant (60fps with will-change hints and compositor-only properties). AI generates none of these.

These rules cover: CSS transitions for simple animations, GPU-accelerated properties, Framer Motion for React, prefers-reduced-motion accessibility, and animation performance optimization.

Rule 1: CSS Transitions for Simple Animations

The rule: 'Use CSS transitions for state-based animations (hover, focus, enter/exit): .button { transition: background-color 150ms ease, transform 150ms ease; } .button:hover { background-color: var(--color-primary-hover); transform: scale(1.02); }. CSS transitions are: declarative (define start and end states, browser handles the in-between), performant (browser-optimized, no JavaScript execution), and simple (two CSS properties replace 10 lines of JavaScript).'

For timing and easing: 'Duration: 150-300ms for micro-interactions (hover, focus, toggle), 300-500ms for layout transitions (expand, collapse, slide), 200ms for opacity fades. Easing: ease-out for entering elements (fast start, gentle stop — feels responsive), ease-in for exiting elements (gentle start, fast finish — moves out of the way), ease-in-out for layout changes (smooth start and stop). Never linear for UI animation — it feels mechanical.'

AI generates: element.style.opacity = 0; setInterval(() => { element.style.opacity = parseFloat(element.style.opacity) + 0.05; }, 16) — 20 lines of JavaScript for a fade-in. CSS: transition: opacity 300ms ease-out; opacity: 1; — two properties. The CSS version is smoother (browser-optimized), more reliable (no timer drift), and blocks zero main thread time.

  • CSS transitions for hover, focus, toggle, enter/exit — state-based animations
  • 150-300ms for micro-interactions, 300-500ms for layout transitions
  • ease-out for entering, ease-in for exiting, ease-in-out for layout changes
  • Never linear for UI — it feels mechanical and unnatural
  • Two CSS properties replace 20 lines of JavaScript animation code
💡 2 Properties vs 20 Lines

AI writes 20 lines of setInterval JavaScript for a fade-in. CSS: transition: opacity 300ms ease-out; opacity: 1; — two properties. Browser-optimized, no timer drift, zero main thread blocking. Same result, 10x less code.

Rule 2: GPU-Accelerated Transform and Opacity

The rule: 'Animate only compositor-friendly properties: transform (translate, scale, rotate) and opacity. These properties are GPU-composited — the browser renders them on a separate layer without recalculating layout or repainting other elements. Never animate: width, height, top, left, margin, padding, border-width, font-size. These trigger layout recalculation for every frame — 60 recalculations per second = jank.'

For the rendering pipeline: 'Browser rendering: JavaScript > Style > Layout > Paint > Composite. Transform and opacity skip Layout and Paint — they only affect the Composite step. Width and height trigger Layout (recalculate positions of all affected elements), then Paint (redraw pixels), then Composite. Skipping Layout and Paint: 10-100x less work per frame. This is why transform: translateX(100px) is smooth and left: 100px is janky.'

AI generates: @keyframes slide { from { left: -100%; } to { left: 0; } } — animating left triggers layout recalculation 60 times per second. Replace with: @keyframes slide { from { transform: translateX(-100%); } to { transform: translateX(0); } } — same visual result, GPU-composited, zero layout recalculation, smooth 60fps.

⚠️ left: 100px = 60 Layout Recalcs

Animating left triggers layout recalculation 60 times per second — every element position recalculated per frame. transform: translateX(100px) skips layout entirely — GPU-composited on a separate layer. Same visual motion, 10-100x less work per frame.

Rule 3: Framer Motion for Complex React Animations

The rule: 'Use Framer Motion for: enter/exit animations (AnimatePresence), layout animations (layoutId for shared element transitions), gesture animations (drag, hover, tap), orchestrated sequences (staggerChildren), and spring physics (natural-feeling motion). Pattern: <motion.div initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, y: -20 }} transition={{ duration: 0.3 }} />.'

For AnimatePresence: 'AnimatePresence enables exit animations — the component animates out before unmounting: <AnimatePresence>{isVisible && <motion.div key="modal" exit={{ opacity: 0 }} />}</AnimatePresence>. Without AnimatePresence: the component disappears instantly on unmount. With it: the exit animation plays, then the component unmounts. This is the most common animation gap in React — enter animations are easy, exit animations require AnimatePresence.'

AI generates: useState + useEffect + setTimeout for animation sequencing — fragile, hard to coordinate, and impossible to interrupt cleanly. Framer Motion: declarative initial/animate/exit states, automatic interpolation, spring physics, and gesture support. One component replaces 30 lines of imperative animation code.

Rule 4: Respect prefers-reduced-motion

The rule: 'Check and respect the user prefers-reduced-motion setting. CSS: @media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; } }. This disables all animations for users who have requested reduced motion — people with vestibular disorders, motion sensitivity, or seizure conditions. In Framer Motion: const prefersReduced = useReducedMotion(); use it to disable or simplify animations.'

For what to reduce vs remove: 'Reduced motion does not mean no motion. Fade transitions (opacity changes) are generally safe — keep them. Remove or reduce: sliding, bouncing, zooming, parallax, auto-playing video, and continuous looping animations. Replace slide-in with fade-in. Replace bounce with instant appearance. The goal: convey the same state change without the motion that triggers discomfort.'

AI generates: elaborate animations with no motion preference check. Users with vestibular disorders experience: nausea, dizziness, and headaches from sliding and bouncing animations. prefers-reduced-motion is set by 20-30% of users (including those who just prefer less distraction). One media query respects all of them.

  • prefers-reduced-motion: reduce — disable or simplify all animations
  • Fade transitions are generally safe — keep opacity changes
  • Remove: sliding, bouncing, zooming, parallax, continuous looping
  • Framer Motion: useReducedMotion() hook for conditional animation
  • 20-30% of users set reduced motion — not a niche preference
ℹ️ 20-30% of Users Set Reduced Motion

prefers-reduced-motion is not a niche setting. It includes vestibular disorder patients (nausea from sliding animations), seizure-sensitive users, and people who simply prefer less distraction. One media query respects all of them.

Rule 5: Animation Performance Optimization

The rule: 'Target 60fps (16.67ms per frame). Tools: Chrome DevTools > Performance > Frames (identify dropped frames), Rendering tab > Paint flashing (visualize repaints), Layer panel (verify GPU layers). Use will-change: transform on elements that will be animated (creates a GPU layer in advance). Remove will-change after the animation completes — permanent will-change wastes GPU memory.'

For animation budgets: 'Set animation budgets per page: maximum 3 simultaneous animations, no animation longer than 500ms (except page transitions), no animation that blocks interaction (runs on compositor, not main thread). Stagger concurrent animations: instead of 5 cards animating simultaneously (GPU spike), stagger by 50ms (smooth cascade). Framer Motion: staggerChildren: 0.05 in the parent transition.'

AI generates: 10 elements animating simultaneously with layout-triggering properties — the main thread is overwhelmed, interactions are blocked, and the frame rate drops to 15fps. Budget: 3 simultaneous transform/opacity animations, staggered by 50ms, with will-change applied just before and removed just after. Same visual richness, 60fps performance.

Complete Animation Patterns Rules Template

Consolidated rules for animation patterns.

  • CSS transitions for simple state animations — hover, focus, toggle, enter/exit
  • Transform and opacity only — GPU-composited, skip layout and paint
  • Never animate width, height, top, left, margin — triggers 60 layout recalcs/second
  • Framer Motion for complex React animations — AnimatePresence for exit animations
  • prefers-reduced-motion: reduce — disable or simplify, keep opacity fades
  • 60fps target: will-change before animation, remove after, max 3 simultaneous
  • Stagger concurrent animations by 50ms — smooth cascade, not GPU spike
  • 150-300ms micro-interactions, 300-500ms layout transitions — ease-out for enter