Design GPU-accelerated animations with Web Animations API (WAAPI), Scroll-Driven Animations (ScrollTimeline/ViewTimeline), FLIP technique, easing systems, and accessibility (prefers-reduced-motion). Use when user asks to create animations, transitions, scroll effects, page transitions, or interactive motion for web UIs. Do NOT use for canvas-based animations (Three.js, PixiJS), video editing, or non-web (native mobile) motion design.
Design GPU-accelerated animations with Web Animations API (WAAPI), Scroll-Driven Animations (ScrollTimeline/ViewTimeline), FLIP technique, easing systems, and accessibility (prefers-reduced-motion). Use when user asks to create animations, transitions, scroll effects, page transitions, or interactive motion for web UIs. Do NOT use for canvas-based animations (Three.js, PixiJS), video editing, or non-web (native mobile) motion design.
Never animate keyboard-initiated actions. These repeat hundreds of times daily.
2. What is the purpose?
Every animation must have a clear answer to "why does this animate?"
Purpose
Example
Valid?
Spatial consistency
Toast enters and exits from the same direction
?
State indication
Button morphs to show "sent"
?
Feedback
Button scales down on press, confirming the interface heard the user
?
Preventing jarring changes
Elements fading in instead of appearing abruptly
?
Explanation
Marketing animation showing how a feature works
?
"It looks cool" AND the user sees it often
Gratuitous motion
?
3. What easing should it use?
Scenario
Easing
CSS
Element entering
ease-out (starts fast, feels responsive)
cubic-bezier(0, 0, 0.2, 1)
Element exiting
ease-in (starts slow, grabs attention briefly then accelerates away)
cubic-bezier(0.4, 0, 1, 1)
On-screen movement / morphing
ease-in-out
cubic-bezier(0.4, 0, 0.2, 1)
Hover / color change
ease
cubic-bezier(0.25, 0.1, 0.25, 1)
Constant motion (marquee, progress bar)
linear
cubic-bezier(0, 0, 1, 1)
UI interactions (buttons, tooltips, dropdowns)
strong ease-out
cubic-bezier(0.23, 1, 0.32, 1)
Critical: never use ease-in for UI entering animations. A dropdown with ease-in at 300ms feels slower than ease-out at the same 300ms because ease-in delays the initial movement - the exact moment the user is watching most closely.
4. How fast should it be?
Element
Duration
Easing
Button press feedback (scale)
100-160ms
cubic-bezier(0.23, 1, 0.32, 1)
Tooltip enter / exit
125-200ms
cubic-bezier(0.23, 1, 0.32, 1)
Dropdown / select / popover
150-250ms
cubic-bezier(0.23, 1, 0.32, 1)
Hover transform (scale, lift)
200ms
cubic-bezier(0.23, 1, 0.32, 1)
Modal / dialog enter
200-300ms
cubic-bezier(0, 0, 0.2, 1)
Modal / dialog exit
150-200ms
cubic-bezier(0.4, 0, 1, 1)
Drawer / sheet enter
200-400ms
cubic-bezier(0.32, 0.72, 0, 1) (iOS-like)
Toast enter
300-400ms
cubic-bezier(0.23, 1, 0.32, 1)
Toast exit
200-300ms
cubic-bezier(0.4, 0, 1, 1)
Page transition
300-400ms
cubic-bezier(0.4, 0, 0.2, 1)
Scroll reveal (fade + slide up)
400-800ms
cubic-bezier(0.16, 1, 0.3, 1)
Loading shimmer
1000-1500ms (infinite)
linear
Hold-to-delete (press)
2000ms
linear (deliberate)
Hold-to-delete (release)
200ms
cubic-bezier(0.23, 1, 0.32, 1) (snappy)
Marketing / explanatory
800-3000ms
variable, depends on narrative
Rule: UI animations should stay under 300ms. A 180ms dropdown feels noticeably more responsive than a 400ms one.
Custom easing curves
The built-in CSS ease / ease-in / ease-out are too weak. Use these stronger custom variants:
When reviewing UI animation code, you MUST use a markdown table with Before | After | Why columns. Do NOT use a list with "Before:" and "After:" on separate lines.
Correct format:
Before
After
Why
transition: all 300ms
transition: transform 200ms ease-out
Specify exact properties; avoid all which triggers layout recalculation
Framer Motion x/y are NOT hardware-accelerated. Use transform string for GPU.
Wrong format (never do this):
Before: transition: all 300ms
After: transition: transform 200ms ease-out
CSS Transitions (preferred for UI)
CSS transitions are interruptible - they can be retargeted mid-animation. Keyframes restart from zero. For any interaction that can be triggered rapidly (toasts, toggles, dropdowns), transitions produce smoother results.
Note: @starting-style and scroll-driven animations (animation-timeline: view(), animation-timeline: scroll()) are Chrome-only as of 2026. Use JavaScript fallbacks for cross-browser support.
WAAPI beats Framer Motion under heavy load - it runs off the main thread. Use for dynamic, interruptible animations where CSS keyframes fall short.
SCROLL-DRIVEN ANIMATIONS
Browser support note: Scroll-driven animations (animation-timeline: view() and scroll()) are Chrome/Edge-only as of 2026, not yet Baseline. Provide JS fallbacks via IntersectionObserver for Firefox/Safari.
Use for: hold-to-delete, image reveals on scroll, comparison sliders, seamless color transitions on tabs.
PERCEIVED PERFORMANCE
Speed in animation directly affects how users perceive your app:
Effect
Mechanism
Fast-spinning spinner
Makes loading feel faster (same load time, different perception)
180ms select vs 400ms select
Shorter animation = perceived responsiveness jump
Instant tooltips after first one
Skip delay + animation on subsequent tooltips
Fast exit animations
Exit must be faster than enter (200ms enter, 150ms exit)
ease-out vs ease-in at same 200ms
ease-out feels faster because user sees immediate movement
PERFORMANCE RULES
Only animate transform and opacity
These skip layout and paint, running on GPU.
CSS variables are inheritable - avoid for animation
// Bad: triggers recalculation on all children
element.style.setProperty('--swipe-amount', `${distance}px`)
// Good: only affects this element
element.style.transform = `translateY(${distance}px)`
CSS animations beat JS under load
When the browser is busy loading a new page, Framer Motion animations (using requestAnimationFrame) drop frames. CSS animations remain smooth - they run off the main thread.
Framer Motion: use transform string, not shorthand
// NOT hardware accelerated (requestAnimationFrame on main thread)
<motion.div animate={{ x: 100 }} />
// Hardware accelerated (GPU, stays smooth even under load)<motion.divanimate={{transform: "translateX(100px)" }} />
Touch devices trigger hover on tap. Gate hover animations behind this media query.
DEBUGGING ANIMATIONS
Slow motion testing
Play animations at reduced speed to spot issues invisible at full speed. Temporarily increase duration to 2-5x normal, or use browser DevTools animation inspector.
Things to look for:
Do colors transition smoothly, or do you see two distinct states overlapping?
Does the easing feel right, or does it start/stop abruptly?
Is the transform-origin correct?
Are multiple animated properties (opacity, transform, color) in sync?
Frame-by-frame inspection
Chrome DevTools Animations panel. Step frame by frame. Reveals timing issues between coordinated properties invisible at full speed.
Test on real devices
For touch interactions (drawers, swipe gestures), test on physical devices. Connect phone via USB, visit local dev server by IP address, use Safari remote devtools.