---
name: react-animations
description: >-
Use when implementing animations, transitions, or motion effects in React apps. Invoke this skill whenever someone asks about animation, transition, motion, framer motion, react spring, GSAP, entrance/exit effects, scroll animation, parallax, gesture, drag, card flip, screen shake, health bar, damage numbers, loading states, page transitions, hover effects, or any element that should move, fade, scale, or respond to interaction — even if they don't use the word "animation".
---
# React Animations
Production-quality animations in React using Framer Motion (primary), React Spring (physics), GSAP (timelines), and CSS/Tailwind (simple cases).
## Library Decision Table
| Use Case | Library | Why |
|---|---|---|
| Entrance/exit animations | Framer Motion | AnimatePresence handles unmount |
| Shared element transitions | Framer Motion | layoutId |
| Physics-based (spring, bounce) | Framer Motion or React Spring | spring config |
| Complex timelines / sequences | GSAP | Timeline API |
| Scroll-triggered | Framer Motion (whileInView / useScroll) | Built-in scroll hooks |
| Simple hover/focus states | CSS Tailwind | No JS needed |
| Drag and drop | Framer Motion | Built-in gesture support |
| SVG path animations | GSAP or Framer Motion | Both support SVG |
| Imperative / programmatic | Framer Motion useAnimate | Modern imperative API |
## Core Principles
> Matt Perry (Framer Motion creator): "Animations should be declared, not imperatively managed. Describe the target state — the library handles the rest."
> Sarah Drasner: "Animation is not decoration — it's communication. Every motion should serve a purpose."
1. **CSS for simple, JS for complex** — if Tailwind `transition` works, use it; don't add Framer Motion for hover states
2. **Only animate composited properties** — `transform` and `opacity`; never `width`, `height`, `top`, `left` (causes reflow)
3. **AnimatePresence wraps conditional renders** — without it, exit animations are skipped
4. **Variants for coordinated animations** — define animation states as objects outside the component, not inline values
5. **layoutId for shared element transitions** — Framer Motion handles the interpolation between positions
6. **useMotionValue for gesture-driven** — don't use useState for values that drive animations
7. **60fps budget** — keep animation logic out of render cycle; use transforms
## Key Framer Motion Patterns
### Basic Animate
```tsx
```
### Gesture States (whileHover, whileTap)
```tsx
```
### Variants with Children Stagger
```tsx
const container = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: { staggerChildren: 0.1 },
},
};
const item = {
hidden: { opacity: 0, y: 20 },
visible: { opacity: 1, y: 0 },
};
{items.map((i) => (
))}
```
### AnimatePresence with Exit
The `mode` prop controls how entering/exiting elements interact:
- `"sync"` (default) — enter and exit happen simultaneously
- `"wait"` — exit completes before enter starts (good for page transitions)
- `"popLayout"` — exiting element is removed from layout flow immediately
```tsx
{isVisible && (
)}
```
### whileInView for Scroll-Triggered Animations
The simplest approach — no scroll hooks needed:
```tsx
```
Use `viewport.once: true` so the animation doesn't replay on scroll back.
### useScroll + useTransform for Parallax
```tsx
const { scrollYProgress } = useScroll();
const y = useTransform(scrollYProgress, [0, 1], [0, -200]);
```
### layoutId Shared Element
```tsx
// In list view
// In detail view
```
### Drag with Constraints
```tsx
```
### useAnimate — Imperative Animations (v11+)
Prefer `useAnimate` over `useAnimation` for programmatic sequences. It's scoped to a ref and works with any selector within that scope:
```tsx
const [scope, animate] = useAnimate();
const handleClick = async () => {
await animate(scope.current, { scale: 1.2 }, { duration: 0.2 });
await animate(scope.current, { scale: 1 }, { duration: 0.1 });
};
```
### MotionConfig — Global Animation Settings
Wrap your app (or a subtree) to set defaults like reduced motion or spring presets:
```tsx
```
`reducedMotion="user"` automatically disables animations for users with OS-level "reduce motion" preferences. This is the easiest way to handle accessibility at scale.
### LazyMotion — Bundle Size Optimization
For production apps, replace `motion` with `LazyMotion` + `m` to code-split the animation engine:
```tsx
import { LazyMotion, domAnimation, m } from 'framer-motion';
```
Use `domMax` instead of `domAnimation` if you need drag or layout animations.
## Game UI Patterns
### Health Bar Smooth Tweening
```tsx
const motionWidth = useMotionValue(current / max);
const springWidth = useSpring(motionWidth, { stiffness: 200, damping: 30 });
useEffect(() => {
motionWidth.set(Math.max(0, Math.min(1, current / max)));
}, [current, max]);
```
### Damage Numbers Floating Up
```tsx
-{damage}
```
### Card Flip (rotateY)
```tsx
```
### Menu Slide-In / Slide-Out
```tsx
{isOpen && (
)}
```
### Inventory Item Drop (Spring Physics)
```tsx
```
## Accessibility
Always respect the user's motion preferences. Two approaches:
**1. MotionConfig (recommended for apps)** — wraps your entire component tree:
```tsx
```
**2. useReducedMotion hook** — for component-level control:
```tsx
const shouldReduceMotion = useReducedMotion();
```
## Performance
- Animate only `transform` and `opacity` — GPU-composited, no layout/paint triggered
- `will-change: transform` for elements that always animate (promotes to own layer)
- Use `motion.create(Component)` to animate custom components without wrapper divs
- `layout` prop triggers automatic layout animations — expensive on large DOM trees; scope to smallest possible subtree
- Prefer `useMotionValue` over `useState` for animation-driving values — motion values don't trigger re-renders
- `LazyMotion` with `domAnimation` saves ~15kb gzip vs the full bundle
## Setup
```bash
bun add framer-motion
```
Zero-config — no providers required. Import and use `motion.div` directly.
For global accessibility handling, wrap your app root:
```tsx
import { MotionConfig } from 'framer-motion';
```
## Boilerplate
`boilerplate/motion-components.tsx` — ready-to-use components: `FadeIn`, `SlideIn`, `ScaleIn`, `StaggerList`, `FloatingNumber`, `HealthBar`, `AnimatedCard`
`templates/animation-variants.ts` — reusable variant objects and spring transition presets
## Cross-References
- `vercel-react-best-practices` — React performance patterns
- `ui-ux-game` — game HUD and UI patterns
- `frontend-design` — component design and Tailwind patterns
## Pitfalls & Anti-Patterns
- **Animating layout-triggering properties** (`width`, `height`, `top`, `left`) — use `scaleX`/`scaleY` or the `layout` prop instead
- **Forgetting AnimatePresence** when using `exit` prop — exit animations silently skip without the wrapper
- **Creating motion values in render** — use `useMotionValue` hook; creating in render causes memory leaks
- **Using CSS `transition` AND Framer Motion on same element** — they conflict; pick one
- **Over-animating** — every interaction animated is sensory overload; animate to communicate, not to decorate
- **Animating on mount without `initial`** — component flashes before animating; always set `initial`
- **Using `useAnimation`** — deprecated; use `useAnimate` for imperative animations instead
- **Large `layout` animations** — `layout` prop on deeply nested trees causes expensive recalculations; scope to smallest possible subtree
- **Skipping accessibility** — always use `MotionConfig reducedMotion="user"` or `useReducedMotion`
## Sources
- Framer Motion documentation — https://motion.dev
- Matt Perry — Framer Motion creator, API design talks
- Sarah Drasner — "SVG Animations", animation design patterns
- GSAP documentation — https://gsap.com