Skip to content
Animation

Framer Motion — Component Library Guide

Production-ready motion library for React. Declarative animations, gestures, layout animations, and exit transitions with a spring-first physics engine.

★ Editor's Pick TypeScript MIT
4.1M weekly downloads
24.5k GitHub stars
~97kB bundle size
v12.4.11 latest version
animationgesturesspringlayoutexit-animations

TL;DR: Framer Motion is the go-to animation library for React. Wrap any element in <motion.div>, pass animate props, and get spring-physics-driven transitions. AnimatePresence handles exit animations that plain CSS can't. At ~97kB it's not tiny, but it's worth it. Official docs: framer.com/motion.

What is Framer Motion?

Framer Motion is a declarative animation library for React that covers the full spectrum from micro-interactions to complex orchestrated sequences. Its spring physics engine produces animations that feel natural rather than mechanical, and its AnimatePresence component solves one of React's hardest problems: animating elements as they leave the DOM.

I used to fake exit animations with a setTimeout and a visible boolean, delaying the actual unmount just long enough for a CSS transition to play. It worked, mostly, until a fast click sequence left ghost elements stuck mid-fade. Swapping that whole pattern for AnimatePresence took about twenty minutes and deleted three separate timeout hacks scattered across the codebase.

When to use it

Use Framer Motion for:

  • Exit animations - CSS can't animate elements being removed from the DOM; Framer can
  • Layout animations - automatically animate position/size changes when items reorder
  • Gesture-driven interactions - drag, swipe, hover with spring physics
  • Complex orchestration - stagger, sequence, and synchronize multiple animations

For simple hover/focus effects, CSS transitions are faster and have zero runtime cost. Framer Motion's ~97kB (gzipped ~30kB) is worth it only when you need what CSS can't do.

Key Features

  • motion.* components - drop-in replacements for any HTML/SVG element
  • AnimatePresence - enables exit animations for unmounted components
  • The layout prop - animates position/size changes automatically
  • Variants - named animation states for orchestrating complex sequences
  • Gestures - whileHover, whileTap, whileDrag with spring physics
  • Scroll animations - useScroll, useTransform for scroll-linked effects

Installation

npm install framer-motion

Basic Usage

import { motion, AnimatePresence } from 'framer-motion'

// Simple enter animation
function FadeIn({ children }: { children: React.ReactNode }) {
  return (
    <motion.div
      initial={{ opacity: 0, y: 10 }}
      animate={{ opacity: 1, y: 0 }}
      transition={{ duration: 0.3 }}
    >
      {children}
    </motion.div>
  )
}

// Exit animation with AnimatePresence
function Toast({ show, message }: { show: boolean; message: string }) {
  return (
    <AnimatePresence>
      {show && (
        <motion.div
          initial={{ opacity: 0, y: -20 }}
          animate={{ opacity: 1, y: 0 }}
          exit={{ opacity: 0, y: -20 }}
        >
          {message}
        </motion.div>
      )}
    </AnimatePresence>
  )
}

Stagger Children with Variants

const container = {
  hidden: { opacity: 0 },
  show: {
    opacity: 1,
    transition: { staggerChildren: 0.1 },
  },
}

const item = {
  hidden: { opacity: 0, x: -20 },
  show: { opacity: 1, x: 0 },
}

function AnimatedList({ items }: { items: string[] }) {
  return (
    <motion.ul variants={container} initial="hidden" animate="show">
      {items.map((text) => (
        <motion.li key={text} variants={item}>
          {text}
        </motion.li>
      ))}
    </motion.ul>
  )
}

Scroll-linked Animations

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

function ParallaxHero() {
  const { scrollY } = useScroll()
  const y = useTransform(scrollY, [0, 500], [0, -150])

  return (
    <motion.div style={{ y }}>
      <img src="/hero.jpg" alt="Hero" />
    </motion.div>
  )
}

Draggable Card with Gesture Constraints

drag plus dragConstraints is the pattern I reach for on swipe-to-dismiss cards and reorderable lists where a full drag-and-drop library would be overkill:

import { motion, useMotionValue, useTransform } from 'framer-motion'

function SwipeToDismissCard({ onDismiss }: { onDismiss: () => void }) {
  const x = useMotionValue(0)
  const opacity = useTransform(x, [-200, 0, 200], [0, 1, 0])

  return (
    <motion.div
      style={{ x, opacity }}
      drag="x"
      dragConstraints={{ left: 0, right: 0 }}
      dragElastic={0.7}
      onDragEnd={(_, info) => {
        if (Math.abs(info.offset.x) > 150) onDismiss()
      }}
    >
      Swipe me away
    </motion.div>
  )
}

dragElastic controls how far the card can travel past the constraint before snapping back, and onDragEnd reads the final offset to decide whether the swipe counted as a dismissal or just a nudge. No separate touch-event listeners, no manual velocity math.

TypeScript Tips

Use Variants type for typed animation states:

import { motion, Variants } from 'framer-motion'

const cardVariants: Variants = {
  offscreen: { y: 50, opacity: 0 },
  onscreen: {
    y: 0,
    opacity: 1,
    transition: { type: 'spring', bounce: 0.4, duration: 0.8 },
  },
}

Common Gotchas

  • AnimatePresence needs a key - the child must have a stable key prop for exit animations to trigger correctly
  • Layout animations and overflow: hidden - parent elements with overflow: hidden clip layout animations; add layout to the parent too or use layoutId
  • Bundle size - if you only need scroll animations, import from framer-motion/client or use the new Motion One alternative
  • initial={false} - pass to AnimatePresence to skip enter animations on first render (important for SSR)
  • React 19 Guide - useOptimistic produces pending states you can animate smoothly with Framer Motion's layout animations
  • Vaul - drawer component with built-in gesture animations; Framer Motion for more custom animation control
  • dnd kit - sortable drag-and-drop; combine with Framer Motion layout prop for animated reordering

Frequently Asked Questions

Is Framer Motion too large for production use?

At roughly 30kB gzipped, Framer Motion is heavier than CSS transitions. It is worth the cost when you need features CSS cannot provide: exit animations for unmounting elements, layout animations, and gesture-driven interactions. For simple hover and focus effects, stick with CSS.

How do I animate an element when it is removed from the DOM?

Wrap the element in AnimatePresence and give it an exit prop with the animation values. AnimatePresence keeps the element mounted long enough to complete the exit animation before removing it from the DOM. The child must have a key prop for this to work.

What is the difference between initial, animate, and exit props?

initial defines the state before the element enters. animate defines the target state it animates to on mount. exit defines the state it animates to before unmounting. These three props cover the full enter-idle-leave lifecycle.

Can I use Framer Motion with React Server Components?

Framer Motion components are client-side only. Mark any file that uses motion components with 'use client'. Static layouts and server components can wrap client components that use Framer Motion without any issues.

What are Framer Motion variants?

Variants are named animation states defined as an object. You pass variant names as strings to initial and animate props instead of inline values. This enables parent-to-child animation orchestration and staggering children without prop drilling.