Framer Motion Examples: 25 Live React Animations

Framer Motion, now published as Motion for React, animates any element you swap for a motion component: set initial, animate and transition props. The 25 recipes below run live on this page, each with copyable code: entrances, stagger, drag, layout and shared elements, exit animations, scroll progress, page transitions and reduced motion.

Install
PackageImport from
motion (current name)motion/react
framer-motion (older name, still published)framer-motion

Updated

Every demo here is the code beside it, running in your browser. Recipes use the motion package; if your project still uses framer-motion, the components and props are the same and only the import path differs.

Want a no-JavaScript effect instead? Try the CSS animation generator.

The API at a glance

Props and hooks used on this page
APIWhat it doesRecipes
initial / animate / exitStart state, target state, leaving state1, 2, 13
transitionDuration, easing, spring settings, delay, repeat5, 24
variantsNamed states that children inherit3
whileHover / whileTap / whileInViewAnimate while a gesture or visibility lasts4, 6
drag / dragConstraintsMake an element draggable inside limits7, 8
layout / layoutIdAnimate size, position and shared elements11, 12, 16
AnimatePresenceKeep leaving children mounted for exit13 to 17
useScroll / useTransformScroll progress and value mapping18, 19
useMotionValue / animate()Values that change without React renders20
useAnimateImperative sequences scoped to a ref23
MotionConfig / useReducedMotionRespect the reduced-motion setting25

Entrances

1. Fade in on mount

initial is the first frame, animate is where it ends. Nothing else is needed for a one-off entrance.

import { motion } from "motion/react";

<motion.div
  initial={{ opacity: 0 }}
  animate={{ opacity: 1 }}
  transition={{ duration: 0.6 }}
>
  Hello
</motion.div>

2. Slide up and fade

Moving with y uses a CSS transform, so the element never pushes its neighbours while it travels.

import { motion } from "motion/react";

<motion.div
  initial={{ opacity: 0, y: 24 }}
  animate={{ opacity: 1, y: 0 }}
  transition={{ type: "spring", stiffness: 260, damping: 22 }}
>
  Card
</motion.div>

3. Staggered list

Variants are named states. The parent passes its state down and staggerChildren delays each child a little more than the last.

import { motion } from "motion/react";

const list = { show: { transition: { staggerChildren: 0.08 } } };
const item = { hidden: { opacity: 0, x: -16 }, show: { opacity: 1, x: 0 } };

<motion.ul variants={list} initial="hidden" animate="show">
  {["Plan", "Design", "Build", "Launch"].map((t) => (
    <motion.li key={t} variants={item}>{t}</motion.li>
  ))}
</motion.ul>

4. Reveal when scrolled into view

whileInView starts the animation when the element enters the viewport; once: true stops it replaying on every scroll.

import { motion } from "motion/react";

<motion.section
  initial={{ opacity: 0, y: 40 }}
  whileInView={{ opacity: 1, y: 0 }}
  viewport={{ once: true, amount: 0.4 }}
>
  Section content
</motion.section>

5. Keyframes

An array is a list of keyframes. times spreads them across the duration, from 0 to 1.

import { motion } from "motion/react";

<motion.div
  animate={{ scale: [1, 1.25, 1], rotate: [0, 0, 180] }}
  transition={{ duration: 1.6, times: [0, 0.5, 1], repeat: Infinity, repeatDelay: 0.6 }}
  style={{ width: 48, height: 48, borderRadius: 12, background: "#4f98fb" }}
/>

Gestures

6. Hover and tap

whileHover and whileTap animate to a state while the gesture lasts, then return on their own.

import { motion } from "motion/react";

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

7. Drag inside a box

dragConstraints can take a ref to a parent element, so the item can never be dragged outside it.

import { motion } from "motion/react";
import { useRef } from "react";

function DragBox() {
  const area = useRef(null);
  return (
    <div ref={area} className="area">
      <motion.div drag dragConstraints={area} dragElastic={0.2} className="ball" />
    </div>
  );
}

8. Swipe a card away

Drag on one axis, then read the offset and velocity in onDragEnd to decide whether the card goes or snaps back.

import { motion } from "motion/react";

<motion.div
  drag="x"
  dragConstraints={{ left: 0, right: 0 }}
  onDragEnd={(e, info) => {
    if (Math.abs(info.offset.x) > 100 || Math.abs(info.velocity.x) > 500) dismiss();
  }}
>
  Swipe me
</motion.div>

9. Drag to reorder

Reorder.Group keeps the array in state and hands you the new order. Each Reorder.Item needs the value it stands for.

import { Reorder } from "motion/react";
import { useState } from "react";

function Todo() {
  const [items, setItems] = useState(["Logo", "Menu", "Hero", "Footer"]);
  return (
    <Reorder.Group axis="y" values={items} onReorder={setItems}>
      {items.map((it) => (
        <Reorder.Item key={it} value={it}>{it}</Reorder.Item>
      ))}
    </Reorder.Group>
  );
}

10. Shake on a wrong answer

Animating x through a few keyframes gives the familiar shake. Trigger it by changing a key or calling animate from useAnimate.

import { motion } from "motion/react";

<motion.input
  key={attempt}
  animate={attempt > 0 ? { x: [0, -10, 10, -6, 6, 0] } : {}}
  transition={{ duration: 0.4 }}
  aria-invalid={attempt > 0}
/>

Layout, exit and page transitions

11. Layout animation

Add layout and change the size or position with ordinary CSS. Motion measures before and after and animates the difference with transforms.

import { motion } from "motion/react";

<motion.div
  layout
  onClick={() => setOpen(!open)}
  style={{ width: open ? 240 : 80, height: open ? 120 : 80 }}
  transition={{ type: "spring", stiffness: 300, damping: 30 }}
/>

12. Shared element (moving tab underline)

Two elements with the same layoutId are treated as one. When the underline renders under a new tab, it glides there.

import { motion } from "motion/react";

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

13. Exit animations with AnimatePresence

React removes elements at once. Wrapping them in AnimatePresence keeps a leaving child mounted until its exit animation ends.

import { AnimatePresence, motion } from "motion/react";

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

14. Modal with a backdrop

Backdrop and panel fade together; the panel also rises. Keep the real dialog semantics (role, focus, Escape) in your own code.

<AnimatePresence>
  {open && (
    <motion.div className="backdrop" onClick={close}
      initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }}>
      <motion.div role="dialog" aria-modal="true" aria-labelledby="m-title"
        onClick={(e) => e.stopPropagation()}
        initial={{ y: 40, opacity: 0 }} animate={{ y: 0, opacity: 1 }} exit={{ y: 40, opacity: 0 }}>
        <h2 id="m-title">Saved</h2>
        <button onClick={close}>Close</button>
      </motion.div>
    </motion.div>
  )}
</AnimatePresence>

15. Accordion with height auto

Motion can animate to height: "auto", which plain CSS transitions cannot. overflow hidden stops the text spilling while it opens.

<button aria-expanded={open} aria-controls="faq-1" onClick={() => setOpen(!open)}>
  What is included?
</button>
<AnimatePresence initial={false}>
  {open && (
    <motion.div id="faq-1" key="body"
      initial={{ height: 0, opacity: 0 }}
      animate={{ height: "auto", opacity: 1 }}
      exit={{ height: 0, opacity: 0 }}
      style={{ overflow: "hidden" }}>
      <p>Hosting, a free address and the editor.</p>
    </motion.div>
  )}
</AnimatePresence>

16. Toast stack

layout on each toast lets the rest slide up smoothly when one leaves. popLayout makes the leaving toast stop taking up space at once.

<ul className="toasts">
  <AnimatePresence mode="popLayout">
    {toasts.map((t) => (
      <motion.li key={t.id} layout
        initial={{ opacity: 0, x: 40 }} animate={{ opacity: 1, x: 0 }} exit={{ opacity: 0, x: 40 }}>
        {t.text}
      </motion.li>
    ))}
  </AnimatePresence>
</ul>

17. Page transitions

mode="wait" lets the old page finish leaving before the new one enters. The key must change with the route.

<AnimatePresence mode="wait">
  <motion.main key={route}
    initial={{ opacity: 0, y: 8 }}
    animate={{ opacity: 1, y: 0 }}
    exit={{ opacity: 0, y: -8 }}
    transition={{ duration: 0.2 }}>
    {pages[route]}
  </motion.main>
</AnimatePresence>

Scroll and motion values

18. Scroll progress bar

useScroll gives scrollYProgress from 0 to 1. Pass container to track a scrolling box instead of the window, and scale the bar with it.

import { motion, useScroll } from "motion/react";
import { useRef } from "react";

function Article() {
  const box = useRef(null);
  const { scrollYProgress } = useScroll({ container: box });
  return (
    <div ref={box} style={{ height: 200, overflowY: "auto" }}>
      <motion.div style={{ scaleX: scrollYProgress, transformOrigin: "0%" }} className="bar" />
      {/* long content */}
    </div>
  );
}

19. Parallax with useTransform

useTransform maps one motion value onto another range, so a layer can move at a different speed from the scroll.

const { scrollYProgress } = useScroll({ container: box });
const y = useTransform(scrollYProgress, [0, 1], [0, -120]);

<motion.img src="/hills.webp" alt="" style={{ y }} />

20. Count up a number

animate() can drive a motion value. useTransform rounds it, and rendering the motion value directly avoids a React render on every frame.

import { animate, motion, useMotionValue, useTransform } from "motion/react";
import { useEffect } from "react";

function Counter({ to }) {
  const n = useMotionValue(0);
  const rounded = useTransform(n, (v) => Math.round(v).toLocaleString());
  useEffect(() => {
    const controls = animate(n, to, { duration: 1.2, ease: "easeOut" });
    return () => controls.stop();
  }, [to]);
  return <motion.span>{rounded}</motion.span>;
}

SVG, loops, sequences and accessibility

21. Draw an SVG line

pathLength runs from 0 to 1 on any path, circle or line, which is the quickest way to draw a tick or a signature.

<svg viewBox="0 0 50 50" width="80">
  <motion.path
    d="M10 26 L21 37 L41 14"
    fill="none" stroke="#16a34a" strokeWidth="4" strokeLinecap="round"
    initial={{ pathLength: 0 }}
    animate={{ pathLength: 1 }}
    transition={{ duration: 0.8, ease: "easeInOut" }}
  />
</svg>

22. Loading spinner

repeat: Infinity with a linear ease gives a steady spin. Pair it with visible or screen-reader text that says what is loading.

<motion.div
  role="status"
  aria-label="Loading"
  animate={{ rotate: 360 }}
  transition={{ repeat: Infinity, duration: 0.9, ease: "linear" }}
  className="spinner"
/>

23. Sequence with useAnimate

useAnimate returns a scope ref and an animate function. Awaiting each call runs steps in order, and stagger spreads one step across many elements.

import { stagger, useAnimate } from "motion/react";

function Sequence() {
  const [scope, animate] = useAnimate();
  async function play() {
    await animate("li", { opacity: 0, x: -20 }, { duration: 0 });
    await animate("li", { opacity: 1, x: 0 }, { delay: stagger(0.1) });
    await animate("button", { scale: [1, 1.1, 1] });
  }
  return (
    <div ref={scope}>
      <ul><li>One</li><li>Two</li><li>Three</li></ul>
      <button onClick={play}>Play</button>
    </div>
  );
}

24. Tuning a spring

Stiffness sets how hard the spring pulls, damping how quickly it settles. Low damping overshoots and wobbles.

<motion.div
  animate={{ x: on ? 160 : 0 }}
  transition={{ type: "spring", stiffness: 120, damping: 8, mass: 1 }}
/>

25. Respecting reduced motion

MotionConfig reducedMotion="user" turns off transform and layout animations for people who ask their system for less motion, while keeping fades.

import { MotionConfig } from "motion/react";

export default function App({ children }) {
  return <MotionConfig reducedMotion="user">{children}</MotionConfig>;
}

// Or decide per component:
const reduce = useReducedMotion();
<motion.div animate={{ x: reduce ? 0 : 100, opacity: 1 }} />

Next.js and bundle size

Next.js App Router: enter animation per page

A template.tsx file remounts on every navigation, unlike layout.tsx, so its initial to animate runs each time you change page. It needs "use client" because it uses motion.

// app/template.tsx
"use client";
import { motion } from "motion/react";

export default function Template({ children }: { children: React.ReactNode }) {
  return (
    <motion.div initial={{ opacity: 0, y: 8 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.25 }}>
      {children}
    </motion.div>
  );
}

Smaller bundles with LazyMotion

The m component plus LazyMotion loads only the features you list. domAnimation covers animations, variants, exit and gestures; domMax adds drag and layout.

import { LazyMotion, domAnimation } from "motion/react";
import * as m from "motion/react-m";

export function Shell({ children }) {
  return (
    <LazyMotion features={domAnimation} strict>
      <m.div animate={{ opacity: 1 }} initial={{ opacity: 0 }}>{children}</m.div>
    </LazyMotion>
  );
}

Choosing between CSS and Motion

Plain CSS transitions are enough for hover colours and simple fades. Reach for Motion when an animation depends on React state, needs an exit, animates layout or height auto, follows a gesture, or links to scroll.

For a single keyframe effect with no logic, our CSS animation generator writes the CSS for you and adds no JavaScript at all.

Common mistakes

Forgetting the key on AnimatePresence children, which makes React reuse the node and skip the exit.

Wrapping a whole page in layout, which makes every small change animate. Put layout only on the element that moves.

Animating box-shadow or filter on long lists. Both repaint each frame; fade a pseudo-element instead.

How it works

  1. Install with npm install motion.
  2. Replace a tag with its motion version, for example div becomes motion.div.
  3. Describe the start and end with initial and animate.
  4. Tune timing with transition, or use a spring.
  5. Wrap conditional elements in AnimatePresence to get exit animations.

Tips

Frequently asked questions

Is Framer Motion the same as Motion?

Yes. The library was renamed Motion and the React part is imported from motion/react. The framer-motion package is still published with the same API, so older tutorials keep working.

Why does my exit animation not run?

The element must be a direct child of AnimatePresence, have a unique key, and have an exit prop. If the parent of AnimatePresence is removed too, nothing is left to animate.

How do I animate height to auto?

Animate height from 0 to "auto" on a motion element and set overflow to hidden. Motion measures the real height for you, which CSS transitions cannot do.

Does Framer Motion work with Next.js Server Components?

Motion components use browser APIs, so the file that renders them needs "use client". A server component can still render that client component and pass it children.

Is Framer Motion bad for performance?

Not when you stick to transforms and opacity, which run without re-layout. Large layout animations and many simultaneous elements cost more; test on a mid-range phone and use LazyMotion to trim the bundle.

Related: CSS animation generator, React Router examples, React icons, UI components, All free tools.

Start for free or see all free tools. We.Inc is free to start, with no card needed. AI building is on paid plans from $20 a month.

More in Guides

We.Inc is an AI-powered website builder you can resell under your own brand. Launch a branded client dashboard, bill on Stripe Connect, and deliver AI-generated websites in minutes. White-label plans start at $99 a month for 25 client sites, with a 7-day free trial and no per-site fees.

Product

Who It's For

Features

Resources

Company

View Sitemap