Framer Motion is the animation library of choice for React in 2025. Its declarative API makes complex animations approachable, but the real power comes from the patterns most developers do not discover until they have used it for months. Here are 10 that we reach for on every project.

1. whileInView for Scroll Reveal

The most common animation pattern on modern websites — elements that animate in as they scroll into the viewport:

<motion.div
  initial={{ opacity: 0, y: 40 }}
  whileInView={{ opacity: 1, y: 0 }}
  viewport={{ once: true }}
  transition={{ duration: 0.6, ease: 'easeOut' }}
>
  {children}
</motion.div>

viewport={{ once: true }} means the animation only plays once — re-entering the viewport does not replay it. This is almost always what you want.

2. Staggered Children With variants

Animate a list of items so each one enters sequentially rather than all at once:

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.id} variants={item}>{i.label}</motion.li>
  ))}
</motion.ul>

3. Layout Animations

Add layout to any element and Framer Motion automatically animates between its previous and next position when it changes. Perfect for reordering lists, expanding/collapsing sections, or moving items between containers:

<motion.div layout>
  {/* Position changes animate automatically */}
</motion.div>

4. Shared Element Transitions With layoutId

layoutIdallows two separate elements in the React tree to animate between each other as if they were the same element. This powers the “expand card to modal” pattern used on many portfolio and product pages:

// Card (list view)
<motion.div layoutId={`card-${id}`} onClick={() => setSelected(id)}>
  <motion.img layoutId={`img-${id}`} src={image} />
</motion.div>

// Modal (detail view) — rendered elsewhere in the tree
{selected === id && (
  <motion.div layoutId={`card-${id}`} className="modal">
    <motion.img layoutId={`img-${id}`} src={image} />
  </motion.div>
)}

5. AnimatePresence for Exit Animations

React removes components from the DOM immediately. AnimatePresence delays the removal until the exit animation completes:

<AnimatePresence>
  {isVisible && (
    <motion.div
      initial={{ opacity: 0 }}
      animate={{ opacity: 1 }}
      exit={{ opacity: 0 }}
    >
      {content}
    </motion.div>
  )}
</AnimatePresence>

6. Spring Physics for Natural Feel

Duration-based easing (ease-in, ease-out) feels mechanical. Springs feel physical. For interactive elements like buttons, modals, and hover states:

transition={{ type: 'spring', stiffness: 400, damping: 30 }}

High stiffness (300–600) = snappy. Low stiffness (50–150) = slow and bouncy. Damping controls the oscillation — higher damping means less bounce.

7. useMotionValue and useTransform for Scroll Effects

const { scrollYProgress } = useScroll()
const opacity = useTransform(scrollYProgress, [0, 0.3], [1, 0])
const y = useTransform(scrollYProgress, [0, 0.3], [0, -60])

<motion.div style={{ opacity, y }}>{hero content}</motion.div>

This creates a parallax hero that fades and moves up as the user scrolls — without any JavaScript event listeners.

8. Gesture Animations

<motion.button
  whileHover={{ scale: 1.05, boxShadow: '0 0 20px rgba(59,130,246,0.5)' }}
  whileTap={{ scale: 0.97 }}
  transition={{ type: 'spring', stiffness: 400, damping: 25 }}
>
  Click Me
</motion.button>

9. useAnimate for Imperative Control

When you need to sequence animations or trigger them programmatically (not on mount or scroll):

const [scope, animate] = useAnimate()

async function handleClick() {
  await animate(scope.current, { x: 100 }, { duration: 0.3 })
  await animate(scope.current, { rotate: 360 }, { duration: 0.5 })
  animate(scope.current, { x: 0, rotate: 0 })
}

<div ref={scope} onClick={handleClick}>...</div>

10. Reduce Motion Accessibility

Some users have vestibular disorders that make animations physically uncomfortable. Always respect the prefers-reduced-motion media query:

const prefersReducedMotion = window.matchMedia(
  '(prefers-reduced-motion: reduce)'
).matches

<motion.div
  animate={{ opacity: 1, y: prefersReducedMotion ? 0 : 0 }}
  initial={{ opacity: 0, y: prefersReducedMotion ? 0 : 40 }}
/>

Or use Framer Motion's built-in hook: useReducedMotion() — it returnstrue when the user prefers reduced motion.


The best animations are the ones users do not consciously notice — they just feel that the interface is smooth, responsive, and alive. Use Framer Motion to earn that feeling.