Lottie animations work natively in React Native — no WebView, no GIFs, no video embeds. This guide covers everything from setup to performance, including the critical differences from the web implementation.
Why Lottie in React Native?
- Native rendering: Lottie animations render via native Android/iOS drawing APIs, not a web view. Smooth on both platforms.
- Tiny files: A 3-second animation is typically 10–40KB — vs 300KB+ for a comparable GIF
- Scriptable: Play, pause, loop, seek, change speed, and even react to gestures
-
Shared source files: The same
.jsonor.lottiefile works on iOS, Android, and the web
Step 0: Preview Before You Build
Before adding any Lottie file to React Native, open it in IconKing:
- See exact colors, timing, and layer structure
- Edit colors to match your app's design system (saves re-exporting from After Effects)
- Convert
.json→.lottiefor smaller bundle size - Catch unsupported After Effects effects before they silently fail on device
No account required, runs entirely in the browser.
Installation
The lottie-react-native library is the standard React Native Lottie renderer:
npm install lottie-react-native
For iOS, install pods:
cd ios && pod install
Minimum version requirements:
- iOS 11+
- Android API 21+ (Android 5.0)
- React Native 0.71+
Basic Usage
import LottieView from 'lottie-react-native';
export default function LoadingScreen() {
return (
<LottieView
source={require('./animations/loading.json')}
autoPlay
loop
style={{ width: 200, height: 200 }}
/>
);
}
The source prop accepts either:
-
require('./file.json')— bundled with the app -
{ uri: 'https://...' }— fetched at runtime
Programmatic Control with ref
import { useRef } from 'react';
import LottieView from 'lottie-react-native';
export default function AnimatedButton({ onPress }) {
const animRef = useRef(null);
return (
<Pressable
onPress={() => {
animRef.current?.play();
onPress?.();
}}
>
<LottieView
ref={animRef}
source={require('./animations/tap.json')}
autoPlay={false}
loop={false}
style={{ width: 48, height: 48 }}
/>
</Pressable>
);
}
Available ref methods:
-
animRef.current.play()— play from current position -
animRef.current.play(startFrame, endFrame)— play a specific segment -
animRef.current.reset()— reset to frame 0 -
animRef.current.pause()— pause at current frame -
animRef.current.resume()— resume from paused position
Loop and Autoplay
{/* Loop forever, start immediately */}
<LottieView source={require('./loading.json')} autoPlay loop />
{/* Play once on mount */}
<LottieView source={require('./success.json')} autoPlay loop={false} />
{/* Manual control only */}
<LottieView ref={animRef} source={require('./hover.json')} autoPlay={false} loop={false} />
Listening for Completion
import { useState } from 'react';
export default function SuccessAnimation({ onDone }) {
return (
<LottieView
source={require('./animations/success.json')}
autoPlay
loop={false}
onAnimationFinish={(isCancelled) => {
if (!isCancelled) onDone?.();
}}
style={{ width: 120, height: 120 }}
/>
);
}
Loading State Pattern
import { useState } from 'react';
import { View, Pressable, Text } from 'react-native';
import LottieView from 'lottie-react-native';
type Status = 'idle' | 'loading' | 'success';
export default function SubmitButton({ onSubmit }) {
const [status, setStatus] = useState<Status>('idle');
async function handlePress() {
setStatus('loading');
try {
await onSubmit();
setStatus('success');
} catch {
setStatus('idle');
}
}
return (
<Pressable
onPress={handlePress}
disabled={status !== 'idle'}
style={styles.button}
>
{status === 'idle' && <Text style={styles.label}>Submit</Text>}
{status === 'loading' && (
<LottieView
source={require('./animations/loading.json')}
autoPlay
loop
style={{ width: 32, height: 32 }}
/>
)}
{status === 'success' && (
<LottieView
source={require('./animations/success.json')}
autoPlay
loop={false}
onAnimationFinish={() => setStatus('idle')}
style={{ width: 32, height: 32 }}
/>
)}
</Pressable>
);
}
Speed and Direction
{/* Half speed */}
<LottieView source={require('./anim.json')} autoPlay speed={0.5} />
{/* Double speed */}
<LottieView source={require('./anim.json')} autoPlay speed={2} />
Changing Colors at Runtime
Use colorFilters to retheme animations without touching the source file:
<LottieView
source={require('./icon.json')}
autoPlay
loop
colorFilters={[
{
keypath: 'Icon Fill', // layer name in After Effects
color: '#FF6B6B', // new color (hex)
},
{
keypath: 'Background',
color: '#1A1A2E',
}
]}
/>
To find the correct layer names, open the file in IconKing and inspect the layer panel — the names map directly to keypath values.
Using dotLottie (.lottie) Format
lottie-react-native v6.0+ supports .lottie files natively:
<LottieView
source={require('./animations/loading.lottie')}
autoPlay
loop
/>
.lottie files are ~75% smaller than .json. Convert at IconKing.
For older versions, use the JSON format.
Expo Setup
In Expo managed workflow, lottie-react-native requires the expo-av or expo plugin:
// app.json
{
"expo": {
"plugins": ["lottie-react-native"]
}
}
Then rebuild:
npx expo run:ios
# or
npx expo run:android
Note: Lottie does not work in Expo Go (the sandbox app). You need a development build.
Performance Tips
1. Use .lottie format
Convert at IconKing — 75% smaller = faster app startup, less memory.
2. Avoid large animations on list items
Rendering a Lottie animation in every cell of a FlatList is expensive. Use simpler static icons for list rows, and reserve animations for standalone screens.
3. Pause when not visible
import { useIsFocused } from '@react-navigation/native';
export default function AnimatedScreen() {
const isFocused = useIsFocused();
return (
<LottieView
source={require('./bg-animation.json')}
autoPlay={isFocused}
loop
/>
);
}
4. Resize correctly
Don't use flex: 1 on a LottieView inside a scroll container — it causes layout thrashing. Always provide explicit width and height.
Common Issues
Animation not showing on iOS
- Run
pod installin the/iosdirectory - Clean build folder (Xcode → Product → Clean Build Folder)
- Rebuild
Colors look wrong on Android
Android and iOS render some After Effects effects differently. Open the file in IconKing to see the browser render. If browser and device differ, the issue is in the export. Ask your designer to avoid blend modes and use flat fills.
onAnimationFinish fires immediately
The animation has 0 frames or an error. Check the file is valid by opening it in IconKing.
White flash before animation starts
Set resizeMode="cover" and add a background color matching your animation's first frame.
Where to Get Lottie Files
- LottieFiles — largest free Lottie library; search by keyword
- Your designer — have them export from After Effects using Bodymovin
- IconKing — preview any file before using, edit colors, convert formats
Summary
-
npm install lottie-react-native+pod installfor iOS - Use
<LottieView source={require(...)} autoPlay loop />for basic use -
refgives you.play(),.pause(),.reset(),.resume() -
onAnimationFinishfires when a non-looping animation completes -
colorFilterschanges colors without touching the source file - Pause with
useIsFocused()when the screen is off-screen - Convert to
.lottieat IconKing for smaller bundle size
Top comments (0)