How to use
- Copy the installation command and review the source package before installing it in your own agent environment.
- Check the source document for required tools, credentials and supported hosts.
- Describe your input and desired deliverable; review the output against the source material.
npx skills add https://github.com/fcsouza/agent-skills --skill react-animationsBefore you start
- Installation does not include a model subscription, API allowance or permission to access your files.
- Repository stars and installation counts describe activity, not an independent quality or security test.
Rendered from the source repository. Headings, examples, code, tables, links, and referenced images are preserved.
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."
- CSS for simple, JS for complex — if Tailwind
transitionworks, use it; don't add Framer Motion for hover states - Only animate composited properties —
transformandopacity; neverwidth,height,top,left(causes reflow) - AnimatePresence wraps conditional renders — without it, exit animations are skipped
- Variants for coordinated animations — define animation states as objects outside the component, not inline values
- layoutId for shared element transitions — Framer Motion handles the interpolation between positions
- useMotionValue for gesture-driven — don't use useState for values that drive animations
- 60fps budget — keep animation logic out of render cycle; use transforms
Key Framer Motion Patterns
Basic Animate
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.3 }}
/>Gesture States (whileHover, whileTap)
<motion.button
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
transition={{ type: 'spring', stiffness: 400, damping: 17 }}
/>Variants with Children Stagger
const container = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: { staggerChildren: 0.1 },
},
};
const item = {
hidden: { opacity: 0, y: 20 },
visible: { opacity: 1, y: 0 },
};
<motion.ul variants={container} initial="hidden" animate="visible">
{items.map((i) => (
<motion.li key={i} variants={item} />
))}
</motion.ul>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
<AnimatePresence mode="wait">
{isVisible && (
<motion.div
key="modal"
initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.9 }}
/>
)}
</AnimatePresence>whileInView for Scroll-Triggered Animations
The simplest approach — no scroll hooks needed:
<motion.div
initial={{ opacity: 0, y: 40 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: '-100px' }}
transition={{ duration: 0.5 }}
/>Use viewport.once: true so the animation doesn't replay on scroll back.
useScroll + useTransform for Parallax
const { scrollYProgress } = useScroll();
const y = useTransform(scrollYProgress, [0, 1], [0, -200]);
<motion.div style={{ y }} />layoutId Shared Element
// In list view
<motion.div layoutId={`card-${id}`}>
<Thumbnail />
</motion.div>
// In detail view
<motion.div layoutId={`card-${id}`}>
<FullImage />
</motion.div>Drag with Constraints
<motion.div
drag
dragConstraints={{ left: -100, right: 100, top: -50, bottom: 50 }}
dragElastic={0.2}
whileDrag={{ scale: 1.1 }}
/>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:
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 });
};
<div ref={scope}>
<button onClick={handleClick}>Click me</button>
</div>MotionConfig — Global Animation Settings
Wrap your app (or a subtree) to set defaults like reduced motion or spring presets:
<MotionConfig reducedMotion="user">
<App />
</MotionConfig>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:
import { LazyMotion, domAnimation, m } from 'framer-motion';
<LazyMotion features={domAnimation}>
<m.div animate={{ opacity: 1 }} />
</LazyMotion>Use domMax instead of domAnimation if you need drag or layout animations.
Game UI Patterns
Health Bar Smooth Tweening
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]);
<motion.div style={{ scaleX: springWidth, transformOrigin: 'left' }} />Damage Numbers Floating Up
<motion.span
initial={{ opacity: 1, y: 0 }}
animate={{ opacity: 0, y: -60 }}
transition={{ duration: 0.8, ease: 'easeOut' }}
onAnimationComplete={onComplete}
>
-{damage}
</motion.span>Card Flip (rotateY)
<motion.div animate={{ rotateY: isFlipped ? 180 : 0 }} style={{ perspective: 1000 }}>
<div style={{ backfaceVisibility: 'hidden' }}>{front}</div>
<div style={{ backfaceVisibility: 'hidden', rotateY: 180 }}>{back}</div>
</motion.div>Screen Shake (useAnimate)
const [scope, animate] = useAnimate();
const shake = async () => {
await animate(scope.current, { x: [0, -10, 10, -10, 10, 0] }, { duration: 0.4 });
};
<div ref={scope}>{children}</div>Menu Slide-In / Slide-Out
<AnimatePresence>
{isOpen && (
<motion.nav
initial={{ x: -300 }}
animate={{ x: 0 }}
exit={{ x: -300 }}
transition={{ type: 'spring', stiffness: 300, damping: 30 }}
/>
)}
</AnimatePresence>Inventory Item Drop (Spring Physics)
<motion.div
initial={{ y: -200, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
transition={{ type: 'spring', stiffness: 400, damping: 15 }}
/>Accessibility
Always respect the user's motion preferences. Two approaches:
1. MotionConfig (recommended for apps) — wraps your entire component tree:
<MotionConfig reducedMotion="user">
<App />
</MotionConfig>2. useReducedMotion hook — for component-level control:
const shouldReduceMotion = useReducedMotion();
<motion.div
animate={{ opacity: 1, y: shouldReduceMotion ? 0 : -20 }}
/>Performance
- Animate only
transformandopacity— GPU-composited, no layout/paint triggered will-change: transformfor elements that always animate (promotes to own layer)- Use
motion.create(Component)to animate custom components without wrapper divs layoutprop triggers automatic layout animations — expensive on large DOM trees; scope to smallest possible subtree- Prefer
useMotionValueoveruseStatefor animation-driving values — motion values don't trigger re-renders LazyMotionwithdomAnimationsaves ~15kb gzip vs the full bundle
Setup
bun add framer-motionZero-config — no providers required. Import and use motion.div directly.
For global accessibility handling, wrap your app root:
import { MotionConfig } from 'framer-motion';
<MotionConfig reducedMotion="user">
<App />
</MotionConfig>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 patternsui-ux-game— game HUD and UI patternsfrontend-design— component design and Tailwind patterns
Pitfalls & Anti-Patterns
- Animating layout-triggering properties (
width,height,top,left) — usescaleX/scaleYor thelayoutprop instead - Forgetting AnimatePresence when using
exitprop — exit animations silently skip without the wrapper - Creating motion values in render — use
useMotionValuehook; 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
useAnimatefor imperative animations instead - Large `layout` animations —
layoutprop on deeply nested trees causes expensive recalculations; scope to smallest possible subtree - Skipping accessibility — always use
MotionConfig reducedMotion="user"oruseReducedMotion
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



