framer-motion, react-spring, react-motion, and react-transition-group are tools for adding movement and transitions to React interfaces. framer-motion and react-spring are modern JavaScript-driven libraries that handle complex physics and gestures. react-transition-group is a lower-level utility for managing CSS-based enter/exit classes. react-motion is a legacy library that pioneered spring physics in React but is no longer the primary choice for new development.
Choosing the right animation library affects how you structure components, manage state, and handle performance. framer-motion, react-spring, react-motion, and react-transition-group all solve movement in React, but they use different engines and API styles. Let's compare how they handle common engineering tasks.
The core difference lies in where the animation calculation happens. Some libraries calculate values in JavaScript on every frame, while others toggle CSS classes and let the browser handle the interpolation.
framer-motion calculates animations in JavaScript using a optimized engine.
// framer-motion: JS-driven animation
import { motion } from "framer-motion";
function Box() {
return <motion.div animate={{ x: 100, opacity: 1 }} />;
}
react-spring calculates animations in JavaScript using spring physics.
// react-spring: JS-driven spring animation
import { useSpring, animated } from "@react-spring/web";
function Box() {
const props = useSpring({ x: 100, opacity: 1 });
return <animated.div style={props} />;
}
react-motion calculates animations in JavaScript using spring physics (Legacy).
// react-motion: JS-driven spring animation (Legacy)
import { Motion, spring } from "react-motion";
function Box() {
return (
<Motion style={{ x: spring(100) }}>
{({ x }) => <div style={{ transform: `translateX(${x}px)` }} />}
</Motion>
);
}
react-transition-group relies on CSS for the actual movement.
// react-transition-group: CSS-driven animation
import { CSSTransition } from "react-transition-group";
function Box({ show }) {
return (
<CSSTransition in={show} timeout={300} classNames="fade">
<div /> {/* CSS handles .fade-enter-active */}
</CSSTransition>
);
}
How you define animation logic changes based on the library's age and design philosophy. Modern libraries favor hooks, while older ones use components or render props.
framer-motion uses special components with animation props.
motion.// framer-motion: Component-based API
<motion.button whileHover={{ scale: 1.1 }} onClick={handleClick} />
react-spring uses hooks combined with animated components.
useSpring to get animated values.animated components to apply those values.// react-spring: Hook-based API
const props = useSpring({ opacity: isOpen ? 1 : 0 });
return <animated.div style={props} />;
react-motion uses a render-prop component pattern.
<Motion> and receive values in a function.// react-motion: Render-prop API
<Motion style={{ opacity: spring(1) }}>
{(styles) => <div style={{ opacity: styles.opacity }} />}
</Motion>
react-transition-group uses wrapper components to manage class lifecycle.
in prop manually to trigger states.// react-transition-group: Lifecycle wrapper API
<CSSTransition in={isVisible} timeout={500} classNames="slide">
<div>Content</div>
</CSSTransition>
Animating layout shifts (like reordering a list) is notoriously difficult. Some libraries automate this using the FLIP technique, while others require manual measurement.
framer-motion supports automatic layout animations.
layout prop to handle position changes automatically.// framer-motion: Automatic layout
<motion.li layoutId={`item-${id}`} onClick={() => setSelected(id)} />
react-spring requires manual measurement for layout changes.
useMeasure to get element bounds.// react-spring: Manual layout with useMeasure
const [ref, { width }] = useMeasure();
const props = useSpring({ width });
return <animated.div ref={ref} style={props} />;
react-motion requires manual measurement for layout changes.
// react-motion: Manual layout
<Motion style={{ width: spring(newWidth) }}>
{({ width }) => <div style={{ width }} />}
</Motion>
react-transition-group does not support JavaScript layout animations.
// react-transition-group: CSS only
/* No JS layout support β must use CSS transitions for width/height */
Using maintained software reduces security risks and ensures compatibility with future React versions. One of these packages is officially considered legacy.
framer-motion is actively maintained.
react-spring is actively maintained.
react-motion is legacy software.
react-spring.react-transition-group is in maintenance mode.
Despite their differences, these libraries share common goals and patterns.
// All libraries rely on React state to trigger changes
const [open, setOpen] = useState(false);
// Animation triggers when 'open' changes
// framer-motion
<motion.div exit={{ opacity: 0 }} />
// react-transition-group
<CSSTransition in={show} classNames="fade" />
// All support TypeScript props
interface Props { isOpen: boolean; }
| Feature | framer-motion | react-spring | react-motion | react-transition-group |
|---|---|---|---|---|
| Engine | JavaScript | JavaScript (Springs) | JavaScript (Springs) | CSS Classes |
| API Style | Declarative Props | Hooks + Animated | Render Props | Wrapper Components |
| Layout | Automatic (layout) | Manual (useMeasure) | Manual | None |
| Status | Active | Active | Legacy | Maintenance |
| Best For | Interactions | Physics | Legacy Support | Simple CSS |
framer-motion is like a high-level animation studio π¬ β it handles the heavy lifting for you. Ideal for dashboards, interactive prototypes, and apps needing gesture support.
react-spring is like a physics engine βοΈ β it gives you control over the forces driving the movement. Perfect for data visualizations and custom physical interactions.
react-transition-group is like a CSS helper π¨ β it bridges React state and stylesheets. Best for simple fades or slides where performance is critical.
react-motion is like a classic car π β it pioneered the space but is outdated. Only use if maintaining existing legacy code.
Final Thought: For most modern applications, framer-motion offers the best balance of power and ease of use. If you need pure physics control, react-spring is the strong alternative. Avoid react-motion in new work.
Choose react-transition-group if you want to keep animation logic in CSS files and only need simple enter/exit transitions. It is suitable for lightweight projects where JavaScript-driven animation is unnecessary.
Choose framer-motion if you need a declarative API with built-in support for layout animations, gestures, and complex sequences. It is ideal for production apps requiring high-fidelity interactions without manual physics tuning.
Choose react-spring if you prefer a physics-based approach where animations are driven by spring constants rather than durations. It fits well when you need fine-grained control over interpolated values and functional composition.
Do not choose react-motion for new projects as it is legacy software superseded by react-spring. Only use it if you are maintaining an older codebase that already depends on its specific render-prop API.
ATTENTION! To address many issues that have come up over the years, the API in v2 and above is not backwards compatible with the original
React addon (v1-stable).For a drop-in replacement for
react-addons-transition-groupandreact-addons-css-transition-group, use the v1 release. Documentation and code for that release are available on thev1-stablebranch.We are no longer updating the v1 codebase, please upgrade to the latest version when possible
A set of components for managing component states (including mounting and unmounting) over time, specifically designed with animation in mind.
TypeScript definitions are published via DefinitelyTyped and can be installed via the following command:
npm install @types/react-transition-group
Clone the repo first:
git@github.com:reactjs/react-transition-group.git
Then run npm install (or yarn), and finally npm run storybook to start a storybook instance that you can navigate to in your browser to see the examples.