| name | mobile-animations |
|---|---|
| description | Add animations to a React Native/Expo or Flutter app. Covers Reanimated 3 (shared values, worklets, gesture-driven), Lottie and Rive for vector animations, implicit and explicit Flutter animations, Hero transitions, and performance best practices. Use when the user wants smooth UI transitions, loading animations, or gesture-driven motion. |
| standards-version | 1.7.0 |
Use this skill when the user:
- Wants to add animations to components, screens, or transitions
- Asks about Reanimated, Lottie, Rive, or Animated API
- Needs gesture-driven animations (swipe to dismiss, drag to reorder)
- Mentions "animation", "transition", "motion", "parallax", "spring", "fade", or "slide"
- Wants loading spinners, skeleton screens, or progress animations
- Framework: Expo (React Native) or Flutter
- Animation type: layout transition, gesture-driven, decorative (Lottie/Rive), or screen transition
- Performance requirements: 60fps target, heavy list animations, or simple fade/slide
-
Choose an animation approach. Each fits different use cases:
Approach Framework Best for Performance Reanimated 3 React Native Gesture-driven, layout, springs UI thread (60fps) Lottie Both Designer-created vector animations Good Rive Both Interactive state-machine animations Good Animated API React Native Simple fade/slide JS thread Implicit animations Flutter Simple property changes Good AnimationController Flutter Complex sequenced animations Good Use Reanimated for anything interactive or performance-critical in RN. Use the built-in Animated API only for simple opacity/translate animations.
-
Set up Reanimated (React Native). Install:
npx expo install react-native-reanimated
Add the Babel plugin in
babel.config.js:module.exports = function (api) { api.cache(true); return { presets: ["babel-preset-expo"], plugins: ["react-native-reanimated/plugin"], }; };
-
Create a Reanimated animation. Fade-in on mount:
import Animated, { useSharedValue, useAnimatedStyle, withTiming, } from "react-native-reanimated"; import { useEffect } from "react"; export function FadeIn({ children }: { children: React.ReactNode }) { const opacity = useSharedValue(0); useEffect(() => { opacity.value = withTiming(1, { duration: 500 }); }, []); const style = useAnimatedStyle(() => ({ opacity: opacity.value, })); return <Animated.View style={style}>{children}</Animated.View>; }
-
Add gesture-driven animation. Swipe to dismiss with Reanimated + Gesture Handler:
npx expo install react-native-gesture-handler
import Animated, { useSharedValue, useAnimatedStyle, withSpring, runOnJS, } from "react-native-reanimated"; import { Gesture, GestureDetector } from "react-native-gesture-handler"; export function SwipeToDismiss({ children, onDismiss, }: { children: React.ReactNode; onDismiss: () => void; }) { const translateX = useSharedValue(0); const gesture = Gesture.Pan() .onUpdate((e) => { translateX.value = e.translationX; }) .onEnd((e) => { if (Math.abs(e.translationX) > 150) { runOnJS(onDismiss)(); } else { translateX.value = withSpring(0); } }); const style = useAnimatedStyle(() => ({ transform: [{ translateX: translateX.value }], })); return ( <GestureDetector gesture={gesture}> <Animated.View style={style}>{children}</Animated.View> </GestureDetector> ); }
-
Add Lottie animations. Install and use a JSON animation file:
npx expo install lottie-react-native
import LottieView from "lottie-react-native"; export function LoadingAnimation() { return ( <LottieView source={require("../assets/loading.json")} autoPlay loop style={{ width: 200, height: 200 }} /> ); }
Download free animations from LottieFiles.
-
Flutter implicit animations. Animate property changes automatically:
AnimatedContainer( duration: const Duration(milliseconds: 300), curve: Curves.easeInOut, width: isExpanded ? 200 : 100, height: isExpanded ? 200 : 100, color: isActive ? Colors.blue : Colors.grey, child: child, )
-
Flutter explicit animations. Full control with AnimationController:
class FadeInWidget extends StatefulWidget { final Widget child; const FadeInWidget({super.key, required this.child}); @override State<FadeInWidget> createState() => _FadeInWidgetState(); } class _FadeInWidgetState extends State<FadeInWidget> with SingleTickerProviderStateMixin { late final AnimationController _controller; late final Animation<double> _opacity; @override void initState() { super.initState(); _controller = AnimationController( duration: const Duration(milliseconds: 500), vsync: this, ); _opacity = Tween<double>(begin: 0, end: 1).animate( CurvedAnimation(parent: _controller, curve: Curves.easeIn), ); _controller.forward(); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return FadeTransition(opacity: _opacity, child: widget.child); } }
-
Hero transitions (Flutter). Shared element transitions between screens:
// Source screen Hero(tag: 'product-${product.id}', child: Image.network(product.imageUrl)) // Destination screen Hero(tag: 'product-${product.id}', child: Image.network(product.imageUrl))
- Reanimated: Getting Started
- React Native Gesture Handler
- Lottie for React Native
- Rive for React Native
- Flutter: Animations
User: "I want a card that fades in when it appears and can be swiped away."
Agent:
- Installs react-native-reanimated and react-native-gesture-handler with
mobile_installDependency - Adds the Reanimated Babel plugin
- Creates a
FadeInwrapper component usinguseSharedValueandwithTiming - Creates a
SwipeToDismisswrapper usingGesture.Pan()with spring-back - Composes them:
<SwipeToDismiss onDismiss={remove}><FadeIn><Card /></FadeIn></SwipeToDismiss> - Runs
mobile_checkBuildHealthto verify the native module setup
| Step | MCP Tool | Description |
|---|---|---|
| Install animation libs | mobile_installDependency |
Install react-native-reanimated, lottie-react-native |
| Generate component | mobile_generateComponent |
Scaffold an animated component with props and styles |
| Verify build | mobile_checkBuildHealth |
Ensure native modules compile after adding Reanimated |
- Missing Babel plugin - Reanimated requires
react-native-reanimated/pluginas the last plugin inbabel.config.js. Without it, worklets crash at runtime. - Animating on the JS thread - The built-in
AnimatedAPI runs on the JS thread and drops frames during heavy work. Use Reanimated for 60fps animations. - Not using
useAnimatedStyle- Passing shared values directly tostyledoes not work. Wrap them inuseAnimatedStylefor Reanimated. - Lottie file size - Large Lottie JSON files (>100KB) slow initial render. Use the
dotLottieformat or lazy-load animations. - Flutter dispose - Forgetting to call
_controller.dispose()indispose()causes memory leaks. Always clean up AnimationControllers. - Reanimated requires a dev build - Reanimated does not work in Expo Go. Use a dev client or EAS Build.
- Mobile Component Patterns - composable component architecture for animated wrappers
- Mobile Performance - avoid inline styles and heavy re-renders that kill animation fps
- Mobile Navigation Setup - screen transition animations in Expo Router