Bagas.
    Blog
    Back to Blog
    July 17, 2024·

    Mastering Framer Motion — Smooth Animations in React

    A complete guide to Framer Motion — from your first animated div to variants, gestures, scroll effects, and layout animations.

    B

    Bagas

    @bagas.co
    Mastering Framer Motion — Smooth Animations in React

    Animation can be the difference between a good UI and an unforgettable one. Framer Motion is the go-to animation library for React — it gives you production-ready animations with remarkably little code. This guide walks you from zero to confident, covering every concept you'll actually use on the job.

    Hit the Replay button on any example below to re-run the animation.

    Installation

    bash
    npm install framer-motion

    No additional configuration needed — it works out of the box with Next.js, Vite, and Create React App.


    1. The motion Component

    Every HTML element has a motion equivalent. Swap <div> for <motion.div> and you unlock the full animation API.

    jsx
    import { motion } from "framer-motion";
     
    // Before
    <div className="box" />
     
    // After — now animatable ✨
    <motion.div className="box" />

    Available for every HTML tag: motion.p, motion.button, motion.img, motion.ul, motion.li, and so on.


    2. initial, animate, and transition

    These three props are the foundation of every Framer Motion animation.

    | Prop | Purpose | |---|---| | initial | The starting state | | animate | The target state to animate toward | | transition | Controls how the animation plays |

    jsx
    <motion.div
      initial={{ opacity: 0, y: 24 }}
      animate={{ opacity: 1, y: 0 }}
      transition={{ duration: 0.5, ease: "easeOut" }}
    >
      I fade in from below ✨
    </motion.div>
    Preview
    ✨
    Animation Complete!

    Faded in using easeOut with a subtle scale effect.


    3. Transitions — Control the Feel

    Framer Motion supports several transition types, each with a distinct character.

    tween — Duration-based

    jsx
    transition={{ type: "tween", duration: 0.4, ease: "easeInOut" }}

    spring — Physics-based (most natural)

    The most satisfying feel. Controlled by stiffness (how fast) and damping (how bouncy).

    jsx
    <motion.button
      initial={{ scale: 0 }}
      animate={{ scale: 1 }}
      transition={{ type: "spring", stiffness: 400, damping: 10 }}
    >
      Spring! 🌱
    </motion.button>
    Preview
    ⚡Springstiffness: 400
    🌊Staggereddelay: 0.12s
    🚀Launcheddamping: 10

    Stiffness & Damping Comparison

    jsx
    // Stiff spring — snappy
    transition={{ type: "spring", stiffness: 600, damping: 30 }}
     
    // Soft spring — floaty
    transition={{ type: "spring", stiffness: 80, damping: 12 }}
     
    // Bouncy spring — playful
    transition={{ type: "spring", stiffness: 300, damping: 8 }}
    Preview
    Snappystiffness: 600 · damping: 30
    Floatystiffness: 80 · damping: 12
    Bouncy 🏀stiffness: 300 · damping: 8

    4. Variants — Scalable Animation State

    Once you animate more than two elements, hardcoding props everywhere becomes messy. Variants let you define named animation states and share them across a component tree.

    jsx
    const containerVariants = {
      hidden: { opacity: 0 },
      visible: {
        opacity: 1,
        transition: { staggerChildren: 0.1 },
      },
    };
     
    const itemVariants = {
      hidden: { opacity: 0, x: -20 },
      visible: { opacity: 1, x: 0 },
    };
     
    function AnimatedList({ items }) {
      return (
        <motion.ul variants={containerVariants} initial="hidden" animate="visible">
          {items.map((item) => (
            <motion.li key={item.id} variants={itemVariants}>
              {item.label}
            </motion.li>
          ))}
        </motion.ul>
      );
    }

    The magic: children inherit initial and animate from the parent automatically. The staggerChildren cascades through every child.

    Preview

    Navigation

    • 🏠DashboardActive
    • 📊Analytics
    • 🗂️Projects
    • ⚙️Settings

    5. Gesture Animations

    Framer Motion makes interactive animations trivial with dedicated gesture props.

    whileHover and whileTap

    jsx
    <motion.button
      whileHover={{ scale: 1.05 }}
      whileTap={{ scale: 0.95 }}
      transition={{ type: "spring", stiffness: 400, damping: 17 }}
    >
      Hover & click me
    </motion.button>
    Preview

    drag

    jsx
    <motion.div
      drag="x"
      dragConstraints={{ left: -120, right: 120 }}
      whileDrag={{ scale: 1.1, rotate: 4 }}
    >
      Drag me ← →
    </motion.div>
    Preview
    ←drag anywhere→

    Drag me!


    6. AnimatePresence — Animate on Enter & Exit

    React removes elements from the DOM instantly. AnimatePresence intercepts that removal and plays an exit animation first.

    jsx
    import { motion, AnimatePresence } from "framer-motion";
     
    function Notification({ show, message }) {
      return (
        <AnimatePresence>
          {show && (
            <motion.div
              key="notification"
              initial={{ opacity: 0, y: -16, scale: 0.95 }}
              animate={{ opacity: 1, y: 0, scale: 1 }}
              exit={{ opacity: 0, y: -16, scale: 0.95 }}
              transition={{ type: "spring", stiffness: 300, damping: 24 }}
            >
              {message}
            </motion.div>
          )}
        </AnimatePresence>
      );
    }

    Key rule: Always give the motion element inside AnimatePresence a stable key prop — it's how Framer Motion tracks which element to animate out.


    7. Scroll Animations with useScroll and useTransform

    Create scroll-driven progress bars, parallax effects, and elements that react to scroll position.

    jsx
    import { useScroll, useTransform, motion } from "framer-motion";
     
    function ParallaxHero() {
      const { scrollY } = useScroll();
     
      // opacity goes 1 → 0 as user scrolls 0 → 300px
      const opacity = useTransform(scrollY, [0, 300], [1, 0]);
     
      // background moves at 40% scroll speed (parallax)
      const y = useTransform(scrollY, [0, 300], [0, 120]);
     
      return (
        <motion.section style={{ opacity, y }}>
          <h1>Scroll to fade me</h1>
        </motion.section>
      );
    }

    Reading Progress Bar

    jsx
    function ReadingProgressBar() {
      const { scrollYProgress } = useScroll();
     
      return (
        <motion.div
          className="fixed top-0 left-0 h-1 bg-primary origin-left"
          style={{ scaleX: scrollYProgress }}
        />
      );
    }

    8. Layout Animations

    One of Framer Motion's most impressive features — animate layout changes automatically.

    jsx
    // Reordering items? just add `layout`
    <motion.div layout>
      {item.name}
    </motion.div>
    jsx
    // Shared element transitions between states
    <motion.img
      layoutId={`product-image-${product.id}`}
      src={product.image}
    />
    Preview
    🎨
    Design
    UI/UX
    ⚙️
    Backend
    Node.js
    📱
    Mobile
    React Native

    9. Real-World Patterns

    Page Transition Wrapper

    jsx
    export function PageTransition({ children }) {
      return (
        <motion.main
          initial={{ opacity: 0, y: 12 }}
          animate={{ opacity: 1, y: 0 }}
          exit={{ opacity: 0, y: -12 }}
          transition={{ duration: 0.25, ease: "easeInOut" }}
        >
          {children}
        </motion.main>
      );
    }

    Staggered Card Grid

    jsx
    const gridVariants = {
      hidden: {},
      visible: { transition: { staggerChildren: 0.08 } },
    };
     
    const cardVariants = {
      hidden: { opacity: 0, scale: 0.92, y: 16 },
      visible: {
        opacity: 1,
        scale: 1,
        y: 0,
        transition: { type: "spring", stiffness: 260, damping: 20 },
      },
    };
     
    function ProjectGrid({ projects }) {
      return (
        <motion.div
          className="grid grid-cols-3 gap-6"
          variants={gridVariants}
          initial="hidden"
          animate="visible"
        >
          {projects.map((project) => (
            <motion.div key={project.id} variants={cardVariants}>
              <h3>{project.name}</h3>
            </motion.div>
          ))}
        </motion.div>
      );
    }

    Animated Accordion Item

    jsx
    function AccordionItem({ title, content }) {
      const [isOpen, setIsOpen] = useState(false);
     
      return (
        <div>
          <motion.button
            onClick={() => setIsOpen(!isOpen)}
            whileTap={{ scale: 0.98 }}
          >
            {title}
            <motion.span
              animate={{ rotate: isOpen ? 180 : 0 }}
              transition={{ type: "spring", stiffness: 300, damping: 24 }}
            >
              ▼
            </motion.span>
          </motion.button>
     
          <AnimatePresence initial={false}>
            {isOpen && (
              <motion.div
                key="content"
                initial={{ height: 0, opacity: 0 }}
                animate={{ height: "auto", opacity: 1 }}
                exit={{ height: 0, opacity: 0 }}
                transition={{ duration: 0.25, ease: "easeInOut" }}
                style={{ overflow: "hidden" }}
              >
                {content}
              </motion.div>
            )}
          </AnimatePresence>
        </div>
      );
    }

    10. Performance Tips

    Animate only GPU-accelerated properties. Stick to transform and opacity — these never trigger layout recalculations.

    jsx
    // ✅ GPU-accelerated — smooth at 60fps
    animate={{ x: 100, y: 50, scale: 1.1, rotate: 5, opacity: 0.8 }}
     
    // ❌ Triggers layout paint — avoid
    animate={{ width: 200, height: 100, top: 50, marginLeft: 20 }}

    Respect user accessibility preferences:

    jsx
    import { useReducedMotion } from "framer-motion";
     
    function HeroCard() {
      const shouldReduce = useReducedMotion();
     
      return (
        <motion.div
          initial={shouldReduce ? {} : { opacity: 0, y: 20 }}
          animate={{ opacity: 1, y: 0 }}
          transition={shouldReduce ? { duration: 0 } : { type: "spring" }}
        />
      );
    }

    Quick Reference

    jsx
    import { motion, AnimatePresence, useScroll, useTransform } from "framer-motion";
     
    // All core props at a glance
    <motion.div
      // Entry & exit states
      initial={{ opacity: 0, scale: 0.9, x: -20 }}
      animate={{ opacity: 1, scale: 1, x: 0 }}
      exit={{ opacity: 0, scale: 0.9 }}
     
      // Transition
      transition={{ type: "spring", stiffness: 260, damping: 20, delay: 0.1 }}
     
      // Gestures
      whileHover={{ scale: 1.04 }}
      whileTap={{ scale: 0.96 }}
      drag="x"
      dragConstraints={{ left: -100, right: 100 }}
     
      // Variants
      variants={myVariants}
     
      // Layout
      layout
      layoutId="unique-shared-id"
    />

    Framer Motion rewards you quickly — a few lines produce animations that would take hundreds of lines of raw CSS. Start with initial and animate, reach for variants when logic repeats, then let AnimatePresence and useScroll handle the polish.

    Happy animating. 🎬

    Back to Blog
    Available for work

    Let's build something together.

    Open to freelance projects, collaborations, and full-time roles.

    Get in touch

    Bagas.

    Fullstack developer building clean, fast, and meaningful web experiences.

    Pages

    • Home
    • Blog

    Connect

    • GitHub
    • Instagram
    • bagaskoro125@gmail.com

    © 2026 Bagas. All rights reserved.

    Next.jsReactTailwind CSSDrizzle ORMTursoNextAuth