framer-motion, react-spring, and react-transition-group are the leading solutions for adding motion to React applications, but they solve different problems. framer-motion offers a declarative API focused on developer experience and complex layout animations. react-spring provides physics-based animations for natural movement and granular control over values. react-transition-group is a low-level utility that manages CSS class switching during component enter/exit phases, often serving as the foundation for other libraries.
Adding motion to a React app improves user experience, but picking the right tool affects maintenance and performance. framer-motion, react-spring, and react-transition-group are the top choices, yet they work in fundamentally different ways. This guide compares their APIs, performance characteristics, and best use cases to help you decide.
The biggest difference lies in how you define movement. framer-motion uses a declarative approach where you state the end result. react-spring uses physics springs to calculate values over time. react-transition-group relies on CSS classes that you define separately.
framer-motion lets you animate props directly on components.
import { motion } from "framer-motion";
function Box() {
return <motion.div animate={{ x: 100, opacity: 1 }} />;
}
react-spring requires you to create a spring hook and pass values to an animated component.
import { useSpring, animated } from "@react-spring/web";
function Box() {
const props = useSpring({ x: 100, opacity: 1 });
return <animated.div style={props} />;
}
react-transition-group toggles CSS classes based on component state.
import { CSSTransition } from "react-transition-group";
import "./styles.css";
function Box({ show }) {
return (
<CSSTransition in={show} timeout={300} classNames="fade">
<div className="box" />
</CSSTransition>
);
}
Animating elements as they enter or leave the DOM is a common requirement. Each library handles this differently, especially regarding elements that are removed from the React tree.
framer-motion uses AnimatePresence to keep components alive until their exit animation finishes.
import { motion, AnimatePresence } from "framer-motion";
function List({ items }) {
return (
<AnimatePresence>
{items.map((item) => (
<motion.div
key={item.id}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
/>
))}
</AnimatePresence>
);
}
react-spring typically uses useTransition to manage mounting and unmounting logic internally.
import { useTransition, animated } from "@react-spring/web";
function List({ items }) {
const transitions = useTransition(items, {
key: (item) => item.id,
from: { opacity: 0 },
enter: { opacity: 1 },
leave: { opacity: 0 },
});
return transitions((style, item) => <animated.div style={style} />);
}
react-transition-group relies on the in prop to trigger classes before the element is removed.
import { TransitionGroup, CSSTransition } from "react-transition-group";
function List({ items }) {
return (
<TransitionGroup>
{items.map((item) => (
<CSSTransition key={item.id} timeout={300} classNames="fade">
<div />
</CSSTransition>
))}
</TransitionGroup>
);
}
Sometimes you need natural movement that mimics real-world physics. Other times, you need precise keyframe control.
framer-motion supports both keyframes and spring transitions via the transition prop.
<motion.div
animate={{ x: 100 }}
transition={{ type: "spring", stiffness: 100 }}
/>
react-spring is built entirely on physics. You configure mass, tension, and friction.
const props = useSpring({
from: { x: 0 },
to: { x: 100 },
config: { mass: 1, tension: 170, friction: 26 },
});
react-transition-group does not support physics. You define timing functions in CSS.
/* styles.css */
.fade-enter-active {
transition: opacity 300ms ease-in-out;
}
Performance matters when animating many elements or running on low-power devices.
framer-motion optimizes renders by batching updates and using layout projections. It handles complex layout shifts efficiently without manual calculation.
react-spring drives animations using requestAnimationFrame and updates styles directly. This avoids React re-renders for every frame, which is great for high-frequency updates.
react-transition-group is the lightest weight. It only toggles classes, leaving the actual animation work to the browser's CSS engine. This is efficient but limits dynamic control.
| Feature | framer-motion | react-spring | react-transition-group |
|---|---|---|---|
| API Style | Declarative Components | Functional Hooks | CSS Class Toggles |
| Physics | Built-in Option | Core Foundation | Not Supported |
| Layout Animation | Automatic | Manual Calculation | Not Supported |
| Exit Animations | AnimatePresence | useTransition | in prop + CSS |
| Bundle Weight | Moderate | Moderate | Very Low |
| Learning Curve | Low | Medium | Low |
Pick the tool that matches your project's complexity and team skills.
framer-motion is the best all-rounder for modern React apps. It handles layout shifts, gestures, and standard animations with minimal code. Use it for dashboards, marketing sites, and interactive UIs where development speed matters.
react-spring shines when you need natural motion or complex chains. If you are building a game-like interface or need to interpolate values manually for performance, this is the right choice.
react-transition-group is still useful for simple cases. If you only need to fade a modal in and out and want to keep animation logic in CSS, this library adds almost no overhead.
Final Thought: All three libraries are stable and maintained. Your decision should depend on whether you prefer declarative syntax, physics control, or CSS-based simplicity.
Choose framer-motion if you need a batteries-included solution with powerful layout animations, gesture support, and a declarative API. It is ideal for product teams that want to ship polished interactions quickly without managing physics configurations manually.
Choose react-spring if your application requires physics-based movements, complex chains of animations, or direct access to animated values for performance-critical interfaces. It suits developers who prefer functional hooks and need fine-grained control over interpolation.
Choose react-transition-group if you only need simple CSS-based enter/exit transitions and want to avoid JavaScript-driven animation logic. It is best for lightweight projects, legacy codebases, or when you want to keep animation styles strictly within your CSS files.
npm install motion
Motion is available for React, JavaScript and Vue.
import { motion } from "motion/react"
function Component() {
return <motion.div animate={{ x: 100 }} />
}
Get started with Motion for React.
Note: Framer Motion is now Motion. Import from motion/react instead of framer-motion.
import { animate } from "motion"
animate("#box", { x: 100 })
Get started with JavaScript.
<script>
import { motion } from "motion-v"
</script>
<template> <motion.div :animate={{ x: 100 }} /> </template>
Get started with Motion for Vue.
Browse 330+ official examples, with copy-paste code that'll level-up your animations whether you're a beginner or an expert.
Over 100 examples come with a full step-by-step tutorial.
A one-time payment, lifetime-updates membership:
Motion is sustainable thanks to the kind support of its sponsors.
Motion powers the animations for all websites built with Framer, the web builder for creative pros. The Motion website itself is built on Framer, for its delightful canvas-based editing and powerful CMS features.
Motion drives the animations on the Cursor homepage, and is working with Cursor to bring powerful AI workflows to the Motion examples and docs.