framer-motion, react-spring, and react-transition-group are animation libraries primarily used in React web applications, while lottie-react-native, react-native-animatable, and react-native-reanimated target React Native. These packages enable developers to implement declarative, performant, and complex animations — from simple fades and transitions to gesture-driven interactions and vector-based Lottie animations. Each library adopts a different mental model: some emphasize physics-based motion (react-spring), others focus on production-ready UI transitions (framer-motion, react-transition-group), and several prioritize native-thread performance in mobile contexts (react-native-reanimated). Choosing the right tool depends heavily on platform (web vs. native), animation complexity, performance requirements, and developer ergonomics.
When building modern React or React Native apps, choosing the right animation library can make the difference between janky interactions and fluid, engaging experiences. The six packages under review serve overlapping but distinct purposes — some are web-only, others native-only, and a few span both. Let’s cut through the noise and compare them based on real engineering trade-offs.
Not all animation libraries work everywhere. This is the first filter.
framer-motion, react-spring, and react-transition-group are designed for React web. While react-spring has experimental React Native support, its primary strength is on the web.
lottie-react-native, react-native-animatable, and react-native-reanimated are React Native–only. They rely on native modules and won’t work in browser environments.
⚠️ Important: Don’t try to share animation logic across platforms using these libraries unless you abstract behind a custom hook or component layer. Their APIs and threading models are fundamentally different.
For basic show/hide or mount/unmount animations, react-transition-group is purpose-built:
// react-transition-group
import { CSSTransition } from 'react-transition-group';
function Fade({ children, in: inProp }) {
return (
<CSSTransition in={inProp} timeout={300} classNames="fade">
{children}
</CSSTransition>
);
}
// Requires corresponding CSS:
// .fade-enter { opacity: 0; }
// .fade-enter-active { opacity: 1; transition: opacity 300ms; }
react-native-animatable offers similar simplicity on native:
// react-native-animatable
import Animatable from 'react-native-animatable';
<Animatable.View animation="fadeIn" duration={300}>
<Text>Hello</Text>
</Animatable.View>
But note: react-native-animatable runs on the JavaScript thread, so it can stutter under load.
react-spring uses spring physics instead of fixed durations:
// react-spring (web)
import { useSpring, animated } from 'react-spring';
function MyComponent() {
const props = useSpring({ opacity: 1, from: { opacity: 0 } });
return <animated.div style={props}>Hello</animated.div>;
}
It also works on React Native with minor syntax changes:
// react-spring (React Native)
import { useSpring, animated } from 'react-spring/native';
const AnimatedView = animated(View);
function MyNativeComponent() {
const props = useSpring({ opacity: 1, from: { opacity: 0 } });
return <AnimatedView style={props}><Text>Hello</Text></AnimatedView>;
}
framer-motion abstracts even further:
// framer-motion
import { motion } from 'framer-motion';
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.3 }}
>
Hello
</motion.div>
It handles layout measurements, drag constraints, and even shared-element transitions between routes with minimal code.
react-native-reanimated requires writing "worklets" — functions that run on the UI thread:
// react-native-reanimated
import { useSharedValue, useAnimatedStyle, withTiming } from 'react-native-reanimated';
function ReanimatedBox() {
const opacity = useSharedValue(0);
const style = useAnimatedStyle(() => {
return { opacity: opacity.value };
});
// Trigger later
opacity.value = withTiming(1, { duration: 300 });
return <Animated.View style={style} />;
}
This avoids the JS-to-native bridge, enabling smooth animations even during heavy computation.
lottie-react-native plays designer-created JSON files:
// lottie-react-native
import LottieView from 'lottie-react-native';
<LottieView
source={require('./animation.json')}
autoPlay
loop
/>
No runtime animation logic — just playback control.
react-transition-group: Zero runtime cost beyond class toggling. Performance depends entirely on your CSS.react-native-animatable: Runs on JavaScript thread — can drop frames under load. Not suitable for critical UI.react-spring: Uses requestAnimationFrame on web; on native, it animates via native drivers but still communicates over the bridge (unless using v9+ with Reanimated backend).framer-motion: Optimized for web, uses hardware acceleration, but complex layout animations can trigger reflows.react-native-reanimated: Animations run on UI thread — no bridge involvement. Best-in-class performance for React Native.lottie-react-native: Renders vector paths natively. Performance is excellent for moderate-complexity animations but can struggle with very dense Lottie files.Need animations tied to user input?
framer-motion supports drag out of the box:<motion.div drag dragConstraints={{ left: 0, right: 0 }} />
react-spring integrates with @use-gesture for pan, pinch, etc.
react-native-reanimated pairs with react-native-gesture-handler for seamless gesture-driven animations:
import { PanGestureHandler } from 'react-native-gesture-handler';
import { useAnimatedGestureHandler } from 'react-native-reanimated';
const gestureHandler = useAnimatedGestureHandler({
onStart: (_, ctx) => { /* ... */ },
onActive: (event, ctx) => { translateX.value = event.translationX; },
});
The other libraries lack deep gesture integration.
react-transition-group and react-native-animatable — just pass strings or props.framer-motion and react-spring — require understanding hooks and animation props, but APIs are intuitive.react-native-reanimated — demands familiarity with shared values, worklets, and thread boundaries. Mistakes lead to silent failures or crashes.lottie-react-native — trivial to use if you have Lottie files; irrelevant otherwise.As of 2024:
react-native-animatable has not seen significant updates in years. While not officially deprecated, it’s effectively in maintenance mode. New projects should evaluate react-native-reanimated or react-native’s built-in Animated API instead.| Package | Platform | Threading | Best For | Learning Curve |
|---|---|---|---|---|
framer-motion | Web | Main + GPU | Declarative UI animations, layout transitions | Low-Moderate |
lottie-react-native | React Native | Native | Playing designer-created vector animations | Low |
react-native-animatable | React Native | JavaScript | Simple entrance animations (legacy use) | Low |
react-native-reanimated | React Native | UI Thread | High-performance, gesture-driven animations | High |
react-spring | Web (+ Native) | rAF / Bridge | Physics-based, cross-platform animations | Moderate |
react-transition-group | Web | CSS-only | Mount/unmount transitions with CSS | Low |
framer-motion for most cases. Fall back to react-transition-group if you’re deeply invested in CSS transitions, or react-spring if you love spring physics.react-native-reanimated for anything performance-sensitive or interactive. Use lottie-react-native only when you have Lottie assets from designers. Avoid react-native-animatable in new projects.react-spring is your only real option — but expect to write platform-specific wrappers for complex cases.Animation is one area where the “right” tool depends entirely on your specific constraints. There’s no universal winner — only the best fit for your team, platform, and performance budget.
Choose react-transition-group for simple, lifecycle-driven enter/exit animations in React web apps — especially when integrating with CSS transitions or keyframes. It doesn’t animate values directly but instead manages DOM state (e.g., adding/removing classes) during component mounting/unmounting. Best suited for modal fades, route transitions, or list item additions/removals where you already have CSS-defined animations and just need reliable lifecycle hooks.
Choose framer-motion for React web projects that need expressive, production-grade animations with minimal boilerplate. It excels at layout transitions, drag interactions, and shared element animations between routes or components. Its high-level API abstracts away much of the complexity of managing animation state, making it ideal for teams prioritizing developer velocity without sacrificing visual polish.
Choose react-native-reanimated for high-performance, gesture-driven, or complex interactive animations in React Native. By running animation logic on the UI thread (via worklets), it avoids JavaScript-to-native bridge bottlenecks, enabling buttery-smooth 60+ FPS animations even during heavy JS load. It’s the go-to for bottom sheets, custom sliders, scroll-linked effects, and any animation tied to user input — but comes with a steeper learning curve due to its imperative and functional programming model.
Choose react-spring when you want physics-based, spring-driven animations that feel natural and responsive across both web and React Native. It uses a shared core that works consistently across platforms and supports advanced interpolation, trailing effects, and async orchestration. Ideal for teams comfortable with hooks and reactive patterns who need fine-grained control over animation curves and timing without dropping down to raw CSS or native APIs.
Choose lottie-react-native when you need to render pre-designed, vector-based animations exported from Adobe After Effects via Bodymovin. It’s perfect for loading indicators, onboarding sequences, or branded micro-interactions where design fidelity matters more than runtime control. Avoid if you need dynamic, code-driven animations — Lottie is best for static or parameterized playback of designer-created assets.
Choose react-native-animatable for simple, declarative entrance/exit animations in React Native apps with low performance demands. It provides a straightforward API for common effects like fade, slide, bounce, and flip using JavaScript-thread animations. However, it should be avoided in performance-critical scenarios since it doesn’t leverage the native thread, and the project has seen minimal recent activity — consider it only for lightweight use cases or legacy maintenance.
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.