Skip to content
Writing

10 min read

How to Use Motion for React (formerly Framer Motion)

Learn how to animate React apps with Motion, formerly Framer Motion. Install it, then add springs, gestures, scroll, exit and layout animations.

  • jsx
  • motion

Motion for React is the library I reach for whenever an interface needs to move. It was called Framer Motion until recently, and it turns animations that would take pages of CSS and state into a few props on a component. In this guide, you'll learn how to use Motion in a React app from scratch: install it, animate your first element, and then work up to springs, gestures, scroll effects, exit animations and layout transitions.

Motion or Framer Motion?

They're the same library. Framer Motion was renamed Motion, and the package changed from framer-motion to motion. If you're following an older tutorial, the only change you need is the import: from 'framer-motion' becomes from 'motion/react'.

What is Motion for React?

Motion is an animation library for React. Its core idea is the motion component: take any HTML or SVG element, write motion.div instead of div, and it gains animation props like animate, whileHover and exit.

Under the hood it animates with hardware-accelerated browser APIs where it can, and falls back to JavaScript for things CSS can't do, like springs and animating between layouts. You get smooth, interruptible animations without managing timers or class names yourself.

How to install Motion in React

Install the package with your package manager:

Terminal
npm install motion

Then import the motion component from motion/react:

jsx
import { motion } from 'motion/react';

That's the whole setup. It works with Vite, Create React App and Next.js. It supports React 18 and React 19.

Your first animation

Give a motion component an initial state, an animate state and a transition, and Motion animates between them when the component mounts.

Box.jsx
import { motion } from 'motion/react';

export function Box() {
  return (
    <motion.div
      initial={{ opacity: 0, y: 20 }}
      animate={{ opacity: 1, y: 0 }}
      transition={{ duration: 0.5 }}
    >
      Hello, Motion
    </motion.div>
  );
}

The box starts invisible and 20px down, then fades in and slides up over half a second. When animate changes later, say from a state update, Motion animates from wherever the element currently is, even mid-animation.

Springs and tweens: how animations feel

transition is where an animation gets its character. There are two main kinds. A tween runs for a set time with an easing curve, and a spring behaves like a physical object, so it can overshoot and settle.

jsx
// A tween: fixed duration and easing
<motion.div animate={{ x: 200 }} transition={{ duration: 0.4, ease: 'easeOut' }} />

// A spring, described by how long it takes and how bouncy it is
<motion.div animate={{ x: 200 }} transition={{ type: 'spring', duration: 0.5, bounce: 0.25 }} />

// A spring, described by physics
<motion.div animate={{ x: 200 }} transition={{ type: 'spring', stiffness: 300, damping: 20 }} />

Springs are worth learning first. They keep their momentum when interrupted, so a hover that gets cancelled halfway still looks natural. For most interface animations I use a spring with a bounce of 0 for a calm settle, or a small bounce for something playful.

Gesture animations: hover, tap and focus

The whileHover, whileTap and whileFocus props animate an element while a gesture is happening, then animate it back when it ends. You don't manage any state for it.

jsx
<motion.button whileHover={{ scale: 1.05 }} whileTap={{ scale: 0.95 }}>
  Save
</motion.button>

A small scale change on press makes a button feel like it responds. Keep it subtle: 0.95 to 0.98 on tap reads as a press, while larger values look exaggerated.

Scroll animations

There are two tools for scrolling. whileInView animates an element when it enters the screen, which is perfect for revealing content as the reader scrolls.

jsx
<motion.section
  initial={{ opacity: 0, y: 24 }}
  whileInView={{ opacity: 1, y: 0 }}
  viewport={{ once: true, margin: '-80px' }}
>
  Content that fades in as you reach it
</motion.section>

Setting once: true plays the animation a single time, and a negative margin waits until the element is a little way into view. For effects tied directly to scroll position, use the useScroll hook. It returns a value from 0 to 1 that you can hand straight to a style, like a reading progress bar:

jsx
import { motion, useScroll } from 'motion/react';

export function ProgressBar() {
  const { scrollYProgress } = useScroll();

  return <motion.div style={{ scaleX: scrollYProgress, transformOrigin: 'left' }} className="progress" />;
}

Variants: animating groups together

Variants are named animation states you define once and share. Their real power is that children inherit the state from their parent, so one prop on the parent can orchestrate a whole list, including a stagger.

jsx
const list = { hidden: {}, show: { transition: { staggerChildren: 0.08 } } };
const item = { hidden: { opacity: 0, y: 12 }, show: { opacity: 1, y: 0 } };

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

The ul switches from hidden to show, and each li follows with an 80ms delay after the one before. There's no per-item delay to calculate.

Exit animations with AnimatePresence

React removes an element from the page the instant it unmounts, so there's nothing left to animate. AnimatePresence fixes that: it keeps a removed child on screen until its exit animation finishes.

jsx
import { AnimatePresence, motion } from 'motion/react';

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

Two things to remember. The animated child must be a direct child of AnimatePresence, and it needs a unique key so React can tell when it's been replaced. When items swap places in a list, add mode="popLayout" so the leaving item steps out of the layout immediately and doesn't push its neighbours around.

Layout animations and shared elements

Adding the layout prop makes an element animate whenever its size or position changes, no matter what caused the change: a new class, a reordered list, a resized container. You change the layout, and Motion animates the difference for you.

jsx
<motion.div layout style={{ width: open ? 300 : 120 }} />

Give two elements the same layoutId, and Motion treats them as one element that moves between the two places. That's how you build a tab underline that slides from tab to tab:

jsx
{tabs.map((tab) => (
  <button key={tab.id} onClick={() => setActive(tab.id)}>
    {tab.label}
    {active === tab.id && <motion.span layoutId="underline" className="underline" />}
  </button>
))}

Motion values for smooth, fast animations

When something has to follow the cursor or a drag, storing it in React state re-renders the component on every frame. Motion values avoid that. They live outside React's render cycle and update the element directly.

jsx
import { motion, useMotionValue, useTransform } from 'motion/react';

const x = useMotionValue(0);
const opacity = useTransform(x, [-100, 0, 100], [0, 1, 0]);

<motion.div drag="x" style={{ x, opacity }} />

Dragging the box changes x, and useTransform derives the opacity from it, so the box fades as it moves away from the centre, without a single re-render.

Respect reduced motion

Some people get dizzy or distracted by motion, and they can ask their device to reduce it. Wrapping your app in MotionConfig makes every animation honour that setting:

jsx
import { MotionConfig } from 'motion/react';

<MotionConfig reducedMotion="user">
  <App />
</MotionConfig>

With reducedMotion="user", movement such as position and scale is skipped, while gentler changes like opacity still animate. Adding it takes a minute, and it's the difference between an animation that's delightful and one that makes someone ill.

Using Motion with Next.js

In the Next.js App Router, components that use hooks or interaction need to be client components, so add 'use client' at the top of the file:

components/fade-in.jsx
'use client';

import { motion } from 'motion/react';

export function FadeIn({ children }) {
  return (
    <motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }}>
      {children}
    </motion.div>
  );
}

If you only need a motion element that animates on mount and want to use it directly in a Server Component, import it from the client entry point instead:

jsx
import * as motion from 'motion/react-client';

export default function Page() {
  return <motion.h1 initial={{ opacity: 0 }} animate={{ opacity: 1 }}>Hello</motion.h1>;
}

Try it: an animated task list

Here's everything together in one small app: a spring on the button, gesture feedback, AnimatePresence for removal and layout so the other tasks glide into place. Add and remove a few tasks and watch how the list responds.

Loading playground…

Common mistakes

  • Animating things that trigger layout. Prefer transform properties (x, y, scale, rotate) and opacity. They stay smooth even when the page is busy.
  • Forgetting the key inside AnimatePresence. Without a unique key, exit animations won't run.
  • Too much motion. If everything animates, nothing stands out. Animate what changes, and keep it short: most interface animations feel best under half a second.
  • Storing animated values in state. Reach for motion values when something updates every frame.

Frequently asked questions

Is Motion the same as Framer Motion?

Yes. Framer Motion was renamed Motion. The library is the same, and you now install motion and import from motion/react.

Is Motion free to use?

Yes. The core library is open source (MIT licensed) and free to use in personal and commercial projects. Motion also sells an optional paid add-on with extra components and examples, but you don't need it for anything in this guide.

Does Motion work with Next.js and the App Router?

Yes. Add 'use client' to components that use motion, or import from motion/react-client to use motion elements in Server Components.

Should I use Motion or CSS animations?

Use CSS for simple transitions like a hover colour change. Reach for Motion when you need springs, exit animations, layout transitions, gestures or animations driven by scroll or drag, which CSS can't do on its own.

Wrapping up

You now know the main pieces of Motion for React: the motion component, springs, gestures, scroll effects, variants, exit and layout animations, and motion values. Start with animate and a spring on something small, like a button or a card, and add more as you find places where movement helps. I use Motion throughout this site, from the sliding hover dot on lists to the floating contents pill in articles, and I wrote about the rest of the stack in How I built my blog with Next.js. If you have questions or want to share what you build, find me on X at @jana__sundar or email me at mailtojana23@gmail.com. Until next time, happy coding!