Skip to main content
Technology & EngineeringAnimation Services249 lines

Framer Motion (Motion)

Production-grade animation library for React — animate, variants, AnimatePresence, layout animations, gestures, scroll-triggered effects, useMotionValue, and stagger orchestration.

Quick Summary29 lines
Framer Motion (now branded as **Motion**) treats animation as a declarative UI concern. Instead of imperative timelines, you describe the desired state and let the library interpolate. Every `motion.*` component is a drop-in replacement for its HTML/SVG counterpart, gaining physics-based animation, gesture recognition, and layout projection for free.

## Key Points

- **Use `layout` sparingly.** Layout projection is powerful but expensive. Apply it only to elements that actually change size/position.
- **Prefer spring transitions.** Springs feel more natural than duration-based easing. Start with `stiffness: 300, damping: 24` and tune from there.
- **Keep `AnimatePresence` close to the conditional.** Wrapping too much of the tree defeats its purpose and can cause flicker.
- **Avoid animating `width`/`height` directly.** Use `layout` or `scale` transforms for better performance since transforms avoid layout thrashing.
- **Use `useMotionValue` for high-frequency updates** like drag or scroll-linked effects to bypass React re-renders.
- **Set `initial={false}` when you do not want the mount animation** to play on first render (e.g., tab switching where the first tab should appear instantly).
- **Use `mode="wait"` on AnimatePresence** when you need the exiting component to leave before the entering one mounts.
- **Animating layout properties via `animate` instead of `layout`.** Directly animating `width` or `left` causes layout recalculations on every frame.
- **Nesting multiple `AnimatePresence` without keys.** Each conditional child inside `AnimatePresence` needs a unique `key` or exit animations silently fail.
- **Creating motion values inside render.** `useMotionValue` must be called at the top level of a component, never inside loops or conditions.
- **Over-animating.** Adding transitions to every element degrades perceived performance. Animate only what draws attention or conveys state change.
- **Using `animate` prop with objects on every render.** This creates a new object reference each render, restarting the animation. Use variants or memoize the object.

## Quick Example

```bash
npm install motion
```

```tsx
// React 18+ — the package re-exports as "motion"
import { motion, AnimatePresence } from "motion/react";
```
skilldb get animation-services-skills/Framer Motion (Motion)Full skill: 249 lines
Paste into your CLAUDE.md or agent config

Framer Motion (Motion)

Core Philosophy

Framer Motion (now branded as Motion) treats animation as a declarative UI concern. Instead of imperative timelines, you describe the desired state and let the library interpolate. Every motion.* component is a drop-in replacement for its HTML/SVG counterpart, gaining physics-based animation, gesture recognition, and layout projection for free.

Key mental model: animate to a target, not through a sequence. Variants let you propagate animation intent through component trees without prop drilling.

Setup

npm install motion
// React 18+ — the package re-exports as "motion"
import { motion, AnimatePresence } from "motion/react";

For projects still on the legacy package:

npm install framer-motion
import { motion, AnimatePresence } from "framer-motion";

Minimal Example

export function FadeIn({ children }: { children: React.ReactNode }) {
  return (
    <motion.div
      initial={{ opacity: 0, y: 12 }}
      animate={{ opacity: 1, y: 0 }}
      transition={{ duration: 0.35, ease: "easeOut" }}
    >
      {children}
    </motion.div>
  );
}

Key Techniques

Variants and Orchestration

Variants decouple animation definitions from JSX. Parent variants automatically propagate to children, enabling stagger with zero wiring.

const containerVariants = {
  hidden: { opacity: 0 },
  visible: {
    opacity: 1,
    transition: {
      staggerChildren: 0.08,
      delayChildren: 0.1,
    },
  },
};

const itemVariants = {
  hidden: { opacity: 0, y: 20 },
  visible: { opacity: 1, y: 0, transition: { type: "spring", stiffness: 300, damping: 24 } },
};

export function StaggeredList({ items }: { items: string[] }) {
  return (
    <motion.ul variants={containerVariants} initial="hidden" animate="visible">
      {items.map((item) => (
        <motion.li key={item} variants={itemVariants}>
          {item}
        </motion.li>
      ))}
    </motion.ul>
  );
}

AnimatePresence — Exit Animations

React unmounts elements immediately. AnimatePresence defers unmount until the exit animation completes.

export function Modal({ isOpen, onClose }: { isOpen: boolean; onClose: () => void }) {
  return (
    <AnimatePresence>
      {isOpen && (
        <motion.div
          key="backdrop"
          className="fixed inset-0 bg-black/40"
          initial={{ opacity: 0 }}
          animate={{ opacity: 1 }}
          exit={{ opacity: 0 }}
          onClick={onClose}
        >
          <motion.div
            key="panel"
            className="mx-auto mt-24 max-w-md rounded-xl bg-white p-6"
            initial={{ scale: 0.92, opacity: 0 }}
            animate={{ scale: 1, opacity: 1 }}
            exit={{ scale: 0.92, opacity: 0 }}
            transition={{ type: "spring", stiffness: 400, damping: 28 }}
            onClick={(e) => e.stopPropagation()}
          >
            {/* content */}
          </motion.div>
        </motion.div>
      )}
    </AnimatePresence>
  );
}

Layout Animations

Adding layout to a motion component enables automatic FLIP animations when its size or position changes in the DOM.

export function ExpandableCard({ expanded, toggle }: { expanded: boolean; toggle: () => void }) {
  return (
    <motion.div
      layout
      onClick={toggle}
      className={expanded ? "w-full h-64" : "w-48 h-32"}
      transition={{ type: "spring", stiffness: 350, damping: 30 }}
    >
      <motion.h2 layout="position">Title</motion.h2>
      <AnimatePresence>
        {expanded && (
          <motion.p
            initial={{ opacity: 0 }}
            animate={{ opacity: 1 }}
            exit={{ opacity: 0 }}
          >
            Expanded content here.
          </motion.p>
        )}
      </AnimatePresence>
    </motion.div>
  );
}

Gestures

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

Scroll-Triggered Animations

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

export function ParallaxHero() {
  const { scrollYProgress } = useScroll();
  const y = useTransform(scrollYProgress, [0, 1], [0, -200]);
  const opacity = useTransform(scrollYProgress, [0, 0.5], [1, 0]);

  return (
    <motion.div style={{ y, opacity }} className="h-screen">
      <h1>Parallax Hero</h1>
    </motion.div>
  );
}

useMotionValue and useTransform

useMotionValue creates a value that updates outside React's render cycle for high-performance animations.

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

export function Slider() {
  const x = useMotionValue(0);
  const background = useTransform(x, [-100, 0, 100], ["#ff0000", "#ffffff", "#00ff00"]);

  return (
    <motion.div style={{ background }} className="p-8">
      <motion.div
        drag="x"
        dragConstraints={{ left: -100, right: 100 }}
        style={{ x }}
        className="h-12 w-12 rounded-full bg-gray-800 cursor-grab"
      />
    </motion.div>
  );
}

Stagger with Custom Delay

const stagger = (index: number) => ({
  hidden: { opacity: 0, x: -20 },
  visible: {
    opacity: 1,
    x: 0,
    transition: { delay: index * 0.06 },
  },
});

export function CustomStagger({ items }: { items: string[] }) {
  return (
    <div>
      {items.map((item, i) => (
        <motion.div key={item} variants={stagger(i)} initial="hidden" animate="visible">
          {item}
        </motion.div>
      ))}
    </div>
  );
}

Best Practices

  • Use layout sparingly. Layout projection is powerful but expensive. Apply it only to elements that actually change size/position.
  • Prefer spring transitions. Springs feel more natural than duration-based easing. Start with stiffness: 300, damping: 24 and tune from there.
  • Keep AnimatePresence close to the conditional. Wrapping too much of the tree defeats its purpose and can cause flicker.
  • Avoid animating width/height directly. Use layout or scale transforms for better performance since transforms avoid layout thrashing.
  • Use useMotionValue for high-frequency updates like drag or scroll-linked effects to bypass React re-renders.
  • Set initial={false} when you do not want the mount animation to play on first render (e.g., tab switching where the first tab should appear instantly).
  • Use mode="wait" on AnimatePresence when you need the exiting component to leave before the entering one mounts.

Anti-Patterns

  • Animating layout properties via animate instead of layout. Directly animating width or left causes layout recalculations on every frame.
  • Nesting multiple AnimatePresence without keys. Each conditional child inside AnimatePresence needs a unique key or exit animations silently fail.
  • Creating motion values inside render. useMotionValue must be called at the top level of a component, never inside loops or conditions.
  • Over-animating. Adding transitions to every element degrades perceived performance. Animate only what draws attention or conveys state change.
  • Using animate prop with objects on every render. This creates a new object reference each render, restarting the animation. Use variants or memoize the object.
  • Ignoring will-change. For complex animations, add will-change: transform via style to hint the browser to promote the element to its own compositing layer.

Install this skill directly: skilldb add animation-services-skills

Get CLI access →