Skip to content
CSS

Framer Motion Guide

Master Framer Motion for React: variants, gestures, layout animations, and exit transitions.

stellae.design

9 min read

Introduction

Framer Motion is the most popular animation library for React. It provides a declarative API for complex animations — layout transitions, gestures, scroll effects, and orchestrated sequences with minimal code.

Unlike CSS animations, Framer Motion handles layout animations, shared element transitions, and physics-based springs out of the box. It also manages exit animations via AnimatePresence.

This guide covers everything from basic animations to advanced production patterns.


Key Concepts

motion Components

typescript
import { motion } from 'framer-motion';

<motion.div
  initial={{ opacity: 0, y: 20 }}
  animate={{ opacity: 1, y: 0 }}
  transition={{ duration: 0.5, ease: 'easeOut' }}
>
  Hello World
</motion.div>

Variants

typescript
const container = {
  hidden: { opacity: 0 },
  show: { opacity: 1, transition: { staggerChildren: 0.1 } },
};
const item = {
  hidden: { opacity: 0, y: 20 },
  show: { opacity: 1, y: 0 },
};

<motion.ul variants={container} initial="hidden" animate="show">
  {items.map(i => <motion.li key={i} variants={item}>{i}</motion.li>)}
</motion.ul>

AnimatePresence

typescript
<AnimatePresence mode="wait">
  {isVisible && (
    <motion.div key="modal"
      initial={{ opacity: 0, scale: 0.95 }}
      animate={{ opacity: 1, scale: 1 }}
      exit={{ opacity: 0, scale: 0.95 }}>
      <Modal />
    </motion.div>
  )}
</AnimatePresence>

Practical Examples

1. Page Transitions

typescript
// app/template.tsx
'use client';
import { motion } from 'framer-motion';

export default function Template({ children }) {
  return (
    <motion.div initial={{ opacity: 0, y: 10 }}
      animate={{ opacity: 1, y: 0 }}
      transition={{ ease: 'easeInOut', duration: 0.3 }}>
      {children}
    </motion.div>
  );
}

2. Gesture Animations

typescript
<motion.button
  whileHover={{ scale: 1.05 }}
  whileTap={{ scale: 0.95 }}
  transition={{ type: 'spring', stiffness: 400, damping: 17 }}>
  Click me
</motion.button>

3. Layout Animations

typescript
function Expandable() {
  const [expanded, setExpanded] = useState(false);
  return (
    <motion.div layout
      onClick={() => setExpanded(!expanded)}
      style={{ width: expanded ? 400 : 200, height: expanded ? 300 : 100 }}
      transition={{ type: 'spring', stiffness: 300, damping: 30 }} />
  );
}

4. Scroll-Triggered

typescript
import { motion, useScroll, useTransform } from 'framer-motion';

function ParallaxHero() {
  const { scrollY } = useScroll();
  const y = useTransform(scrollY, [0, 500], [0, -150]);
  const opacity = useTransform(scrollY, [0, 300], [1, 0]);
  return <motion.div style={{ y, opacity }}><h1>Hero</h1></motion.div>;
}

Best Practices

  • ✅ Use variants for coordinated multi-element animations
  • ✅ Prefer spring transitions for natural feel
  • ✅ Use layout prop for DOM reflow animations
  • ✅ Wrap conditionals with AnimatePresence for exit animations
  • ✅ Use useReducedMotion() for accessibility
  • ❌ Don't animate layout properties without the layout prop
  • ❌ Don't over-animate — subtle is better

Common Pitfalls

  • 🚫 Missing key prop with AnimatePresence
  • 🚫 Animating expensive properties (filter, box-shadow)
  • 🚫 Not respecting prefers-reduced-motion
  • 🚫 Overusing bouncy springs without tuning

CSScss

Was this article helpful?