React Native Animation
Implements performant animations in React Native using Reanimated 3 for UI thread execution, the Animated API with native driver for simpler cases, gesture-driven interactions via react-native-gesture-handler, and LayoutAnimation for implicit transitions. Keeps animations running at 60fps without blocking the JS thread.
TL;DR Checklist
When to Use
Use this skill when:
- Building gesture-driven interactions (drag, pinch, swipe, rotate)
- Implementing shared element transitions between screens
- Creating micro-interactions (button press, card flip, list reorder)
- Animating component mount/unmount with enter/exit transitions
- Adding scroll-driven animations (parallax, header collapse, progress bars)
- Building skeleton loading animations or shimmer effects
When NOT to Use
Avoid this skill for:
- Simple opacity or transform transitions — use Animated API (lighter weight)
- CSS-animatable properties in React Native Web (prefer CSS animations)
- Navigation transitions already handled by React Navigation
- Layout changes that don't require animation (use plain state toggles)
- Situations where InteractionManager is sufficient for deferring work
Core Workflow
Choose Animation Library — Reanimated for complex/interactive animations (UI thread), Animated API for simple declarative animations with native driver.
Set Up Shared/Animated Values — useSharedValue (Reanimated) or useRef(new Animated.Value()) for Animated API.
Define Animated Styles — useAnimatedStyle (Reanimated) or interpolate values to style props.
Trigger with Timing/Spring/Gestures — Apply withTiming/withSpring for declarative curves, or attach to gesture handlers.
Wire Up Gesture Handlers — Use react-native-gesture-handler PanGestureHandler, PinchGestureHandler, etc. with Reanimated worklets.
Clean Up — Cancel running animations, remove listeners, and reset refs on unmount.
Implementation Patterns
Pattern 1: Reanimated with Gesture Handler (Fade-In Card)
// ✅ GOOD: Reanimated on UI thread — smooth 60fps
import Animated, {
useSharedValue,
useAnimatedStyle,
withTiming,
withSpring,
Easing,
} from 'react-native-reanimated';
import { TapGestureHandler, State } from 'react-native-gesture-handler';
function AnimatedCard() {
const opacity = useSharedValue(0);
const scale = useSharedValue(1);
// Mount animation
useEffect(() => {
opacity.value = withTiming(1, {
duration: 400,
easing: Easing.out(Easing.cubic),
});
}, []);
// Press animation with spring
const animatedStyle = useAnimatedStyle(() => ({
opacity: opacity.value,
transform: [{ scale: scale.value }],
}));
const GestureEvent) => {
if (event.nativeEvent.state === State.BEGAN) {
scale.value = withSpring(0.96);
} else if (event.nativeEvent.state === State.END) {
scale.value = withSpring(1);
}
};
return (
<TapGestureHandler
<Animated.View style={[styles.card, animatedStyle]}>
<Text>Animated Card Content</Text>
</Animated.View>
</TapGestureHandler>
);
}
Pattern 2: Animated API with Native Driver (BAD vs. GOOD)
// ❌ BAD: Animating layout property on JS thread — janky
const animatedValue = useRef(new Animated.Value(0)).current;
const startBadAnimation = () => {
Animated.timing(animatedValue, {
toValue: 100,
duration: 300,
useNativeDriver: false, // Layout props require JS thread
}).start();
};
return (
<Animated.View style={{ width: animatedValue }}> // width = JS thread
<Text>Bad — animates width on JS thread</Text>
</Animated.View>
);
// ✅ GOOD: Animating transform on native thread — smooth 60fps
const translateX = useRef(new Animated.Value(0)).current;
const opacity = useRef(new Animated.Value(1)).current;
const startGoodAnimation = () => {
Animated.parallel([
Animated.timing(translateX, {
toValue: 100,
duration: 300,
useNativeDriver: true, // transform supports native driver
}),
Animated.timing(opacity, {
toValue: 0.5,
duration: 300,
useNativeDriver: true, // opacity supports native driver
}),
]).start();
};
return (
<Animated.View
style={{
opacity,
transform: [{ translateX }],
}}
>
<Text>Good — animates transform on native thread</Text>
</Animated.View>
);
Pattern 3: LayoutAnimation for Implicit Transitions
// ✅ GOOD: LayoutAnimation handles the implied layout change smoothly
import { LayoutAnimation, Platform, UIManager } from 'react-native';
// Required for Android
if (
Platform.OS === 'android' &&
UIManager.setLayoutAnimationEnabledExperimental
) {
UIManager.setLayoutAnimationEnabledExperimental(true);
}
function ExpandableSection({ title, children }: ExpandableProps) {
const [isExpanded, setIsExpanded] = useState(false);
const toggleExpand = () => {
LayoutAnimation.configureNext(
LayoutAnimation.create(
300,
LayoutAnimation.Types.easeInEaseOut,
LayoutAnimation.Properties.opacity
)
);
setIsExpanded((prev) => !prev);
};
return (
<View>
<TouchableOpacity
<Text style={styles.title}>{title}</Text>
</TouchableOpacity>
{isExpanded && <View>{children}</View>}
</View>
);
}
Pattern 4: InteractionManager for Deferred Work
// ✅ GOOD: Defer non-critical work until after animations complete
function ProfileScreen({ userId }: { userId: string }) {
const [analyticsLoaded, setAnalyticsLoaded] = useState(false);
useEffect(() => {
const task = InteractionManager.runAfterInteractions(() => {
// This runs after all animations and transitions complete
loadAnalytics(userId).then(() => setAnalyticsLoaded(true));
});
return () => task.cancel();
}, [userId]);
return (
<View>
<ProfileHeader userId={userId} />
{analyticsLoaded ? (
<AnalyticsCharts userId={userId} />
) : (
<ActivityIndicator />
)}
</View>
);
}
Constraints
MUST DO
- Use Reanimated (useSharedValue, useAnimatedStyle) for complex animations on the UI thread
- Set
useNativeDriver: true for transform and opacity animations when using Animated API
- Use
LayoutAnimation.configureNext for implied layout changes (add/remove elements)
- Use
InteractionManager.runAfterInteractions to defer data fetching and analytics
- Animate only
transform (translateX, translateY, scale, rotate) and opacity for native-driver support
- Cancel running animations and remove event listeners on component unmount
MUST NOT DO
- Animate layout properties (width, height, top, left) on the JS thread — use
transform: [{ translateX }]
- Create a new
Animated.Value on every render — store in ref or use useSharedValue
- Block the UI thread with heavy computation during active animations
- Forget to clean up animation refs, listener subscriptions, or gesture handlers on unmount
- Use
useNativeDriver: false unless animating non-transform/non-opacity properties
- Apply Reanimated worklets to callbacks that touch JS state — worklets run on the UI thread
Related Skills
| Skill |
Purpose |
react-native-list-performance |
Animate list item enter/exit with Reanimated layout transitions |
react-native-navigation |
Shared element transitions and screen animations |
react-native-ui-patterns |
Animated theme toggles and responsive layout transitions |
Live References
Authoritative documentation links for this skill's domain. The model follows markdown links at load time to resolve external references and inline content.
1---2name: react-native-animation3description: Implements smooth, performant animations in React Native using Reanimated for UI thread animations, Animated API with native driver, gesture-driven interactions, and layout animations.4license: MIT5---67# React Native Animation89Implements performant animations in React Native using Reanimated 3 for UI thread execution, the Animated API with native driver for simpler cases, gesture-driven interactions via react-native-gesture-handler, and LayoutAnimation for implicit transitions. Keeps animations running at 60fps without blocking the JS thread.1011## TL;DR Checklist1213- [ ] Use Reanimated (useSharedValue + useAnimatedStyle) for all interactive animations14- [ ] Set useNativeDriver: true for transform/opacity animations with Animated API15- [ ] Never animate layout properties (width, height, top, left) on the JS thread16- [ ] Use LayoutAnimation for implied layout changes (add/remove items)17- [ ] Clean up all animation refs and listeners on component unmount18- [ ] Use InteractionManager.runAfterInteractions for deferred non-critical work1920---2122## When to Use2324Use this skill when:2526- Building gesture-driven interactions (drag, pinch, swipe, rotate)27- Implementing shared element transitions between screens28- Creating micro-interactions (button press, card flip, list reorder)29- Animating component mount/unmount with enter/exit transitions30- Adding scroll-driven animations (parallax, header collapse, progress bars)31- Building skeleton loading animations or shimmer effects3233---3435## When NOT to Use3637Avoid this skill for:3839- Simple opacity or transform transitions — use Animated API (lighter weight)40- CSS-animatable properties in React Native Web (prefer CSS animations)41- Navigation transitions already handled by React Navigation42- Layout changes that don't require animation (use plain state toggles)43- Situations where InteractionManager is sufficient for deferring work4445---4647## Core Workflow48491. **Choose Animation Library** — Reanimated for complex/interactive animations (UI thread), Animated API for simple declarative animations with native driver.50512. **Set Up Shared/Animated Values** — useSharedValue (Reanimated) or useRef(new Animated.Value()) for Animated API.52533. **Define Animated Styles** — useAnimatedStyle (Reanimated) or interpolate values to style props.54554. **Trigger with Timing/Spring/Gestures** — Apply withTiming/withSpring for declarative curves, or attach to gesture handlers.56575. **Wire Up Gesture Handlers** — Use react-native-gesture-handler PanGestureHandler, PinchGestureHandler, etc. with Reanimated worklets.58596. **Clean Up** — Cancel running animations, remove listeners, and reset refs on unmount.6061---6263## Implementation Patterns6465### Pattern 1: Reanimated with Gesture Handler (Fade-In Card)6667```tsx68// ✅ GOOD: Reanimated on UI thread — smooth 60fps69import Animated, {70 useSharedValue,71 useAnimatedStyle,72 withTiming,73 withSpring,74 Easing,75} from 'react-native-reanimated';76import { TapGestureHandler, State } from 'react-native-gesture-handler';7778function AnimatedCard() {79 const opacity = useSharedValue(0);80 const scale = useSharedValue(1);8182 // Mount animation83 useEffect(() => {84 opacity.value = withTiming(1, {85 duration: 400,86 easing: Easing.out(Easing.cubic),87 });88 }, []);8990 // Press animation with spring91 const animatedStyle = useAnimatedStyle(() => ({92 opacity: opacity.value,93 transform: [{ scale: scale.value }],94 }));9596 const onGestureEvent = (event: GestureEvent) => {97 if (event.nativeEvent.state === State.BEGAN) {98 scale.value = withSpring(0.96);99 } else if (event.nativeEvent.state === State.END) {100 scale.value = withSpring(1);101 }102 };103104 return (105 <TapGestureHandler onHandlerStateChange={onGestureEvent}>106 <Animated.View style={[styles.card, animatedStyle]}>107 <Text>Animated Card Content</Text>108 </Animated.View>109 </TapGestureHandler>110 );111}112```113114### Pattern 2: Animated API with Native Driver (BAD vs. GOOD)115116```tsx117// ❌ BAD: Animating layout property on JS thread — janky118const animatedValue = useRef(new Animated.Value(0)).current;119120const startBadAnimation = () => {121 Animated.timing(animatedValue, {122 toValue: 100,123 duration: 300,124 useNativeDriver: false, // Layout props require JS thread125 }).start();126};127128return (129 <Animated.View style={{ width: animatedValue }}> // width = JS thread130 <Text>Bad — animates width on JS thread</Text>131 </Animated.View>132);133```134135```tsx136// ✅ GOOD: Animating transform on native thread — smooth 60fps137const translateX = useRef(new Animated.Value(0)).current;138const opacity = useRef(new Animated.Value(1)).current;139140const startGoodAnimation = () => {141 Animated.parallel([142 Animated.timing(translateX, {143 toValue: 100,144 duration: 300,145 useNativeDriver: true, // transform supports native driver146 }),147 Animated.timing(opacity, {148 toValue: 0.5,149 duration: 300,150 useNativeDriver: true, // opacity supports native driver151 }),152 ]).start();153};154155return (156 <Animated.View157 style={{158 opacity,159 transform: [{ translateX }],160 }}161 >162 <Text>Good — animates transform on native thread</Text>163 </Animated.View>164);165```166167### Pattern 3: LayoutAnimation for Implicit Transitions168169```tsx170// ✅ GOOD: LayoutAnimation handles the implied layout change smoothly171import { LayoutAnimation, Platform, UIManager } from 'react-native';172173// Required for Android174if (175 Platform.OS === 'android' &&176 UIManager.setLayoutAnimationEnabledExperimental177) {178 UIManager.setLayoutAnimationEnabledExperimental(true);179}180181function ExpandableSection({ title, children }: ExpandableProps) {182 const [isExpanded, setIsExpanded] = useState(false);183184 const toggleExpand = () => {185 LayoutAnimation.configureNext(186 LayoutAnimation.create(187 300,188 LayoutAnimation.Types.easeInEaseOut,189 LayoutAnimation.Properties.opacity190 )191 );192 setIsExpanded((prev) => !prev);193 };194195 return (196 <View>197 <TouchableOpacity onPress={toggleExpand}>198 <Text style={styles.title}>{title}</Text>199 </TouchableOpacity>200 {isExpanded && <View>{children}</View>}201 </View>202 );203}204```205206### Pattern 4: InteractionManager for Deferred Work207208```tsx209// ✅ GOOD: Defer non-critical work until after animations complete210function ProfileScreen({ userId }: { userId: string }) {211 const [analyticsLoaded, setAnalyticsLoaded] = useState(false);212213 useEffect(() => {214 const task = InteractionManager.runAfterInteractions(() => {215 // This runs after all animations and transitions complete216 loadAnalytics(userId).then(() => setAnalyticsLoaded(true));217 });218219 return () => task.cancel();220 }, [userId]);221222 return (223 <View>224 <ProfileHeader userId={userId} />225 {analyticsLoaded ? (226 <AnalyticsCharts userId={userId} />227 ) : (228 <ActivityIndicator />229 )}230 </View>231 );232}233```234235---236237## Constraints238239### MUST DO240- Use Reanimated (useSharedValue, useAnimatedStyle) for complex animations on the UI thread241- Set `useNativeDriver: true` for transform and opacity animations when using Animated API242- Use `LayoutAnimation.configureNext` for implied layout changes (add/remove elements)243- Use `InteractionManager.runAfterInteractions` to defer data fetching and analytics244- Animate only `transform` (translateX, translateY, scale, rotate) and `opacity` for native-driver support245- Cancel running animations and remove event listeners on component unmount246247### MUST NOT DO248- Animate layout properties (width, height, top, left) on the JS thread — use `transform: [{ translateX }]`249- Create a new `Animated.Value` on every render — store in ref or use useSharedValue250- Block the UI thread with heavy computation during active animations251- Forget to clean up animation refs, listener subscriptions, or gesture handlers on unmount252- Use `useNativeDriver: false` unless animating non-transform/non-opacity properties253- Apply Reanimated worklets to callbacks that touch JS state — worklets run on the UI thread254255---256257## Related Skills258259| Skill | Purpose |260|---|---|261| `react-native-list-performance` | Animate list item enter/exit with Reanimated layout transitions |262| `react-native-navigation` | Shared element transitions and screen animations |263| `react-native-ui-patterns` | Animated theme toggles and responsive layout transitions |264265---266267## Live References268269> Authoritative documentation links for this skill's domain. The model follows markdown links at load time to resolve external references and inline content.270271- [React Native Reanimated 3 Documentation](https://docs.swmansion.com/react-native-reanimated/)272- [React Native Animated API Reference](https://reactnative.dev/docs/animated)273- [React Native LayoutAnimation](https://reactnative.dev/docs/layoutanimation)274- [InteractionManager](https://reactnative.dev/docs/interactionmanager)275- [react-native-gesture-handler](https://docs.swmansion.com/react-native-gesture-handler/)276- [React Native Performance: Using Native Driver](https://reactnative.dev/docs/animations#using-the-native-driver)277- [Reanimated Shared Value Transitions](https://docs.swmansion.com/react-native-reanimated/docs/core/useSharedValue)