Animating elements between screens
This guide covers how to animate elements between screens. This feature is known as a Shared Element Transition and it's implemented in the @react-navigation/native-stack with React Native Reanimated.
Shared Element Transitions are an experimental feature not recommended for production use yet.
Reanimated 4 supports Shared Element Transitions on the New Architecture (Fabric) since 4.2.0, but the feature is behind a feature flag. You need to enable the ENABLE_SHARED_ELEMENT_TRANSITIONS feature flag to use it.
Check the Reanimated documentation for details and send feedback to the Reanimated team
Pre-requisites
Before continuing this guide make sure your app meets these criteria:
- You are using
@react-navigation/native-stack. JS-based@react-navigation/stackor other navigators are not supported. - You have
react-native-reanimatedv4.2.0 or higher installed and configured. - You have enabled the
ENABLE_SHARED_ELEMENT_TRANSITIONSfeature flag.
Minimal example
To create a shared transition:
- Use
Animatedcomponents imported fromreact-native-reanimated. - Assign the same
sharedTransitionTagto elements on different screens. - Navigate between screens. The transition will start automatically.
- Static
- Dynamic
import * as React from 'react';
import { View, StyleSheet } from 'react-native';
import {
useNavigation,
createStaticNavigation,
} from '@react-navigation/native';
import {
createNativeStackNavigator,
createNativeStackScreen,
} from '@react-navigation/native-stack';
import { Button } from '@react-navigation/elements';
import Animated from 'react-native-reanimated';
function HomeScreen() {
const navigation = useNavigation();
return (
<View style={styles.container}>
<Button onPress={() => navigation.navigate('Details')}>
Go to Details
</Button>
<Animated.Image
source={{ uri: 'https://picsum.photos/id/39/200' }}
style={{ width: 300, height: 300 }}
sharedTransitionTag="tag"
/>
</View>
);
}
function DetailsScreen() {
const navigation = useNavigation();
return (
<View style={styles.container}>
<Button onPress={() => navigation.goBack()}>Go back</Button>
<Animated.Image
source={{ uri: 'https://picsum.photos/id/39/200' }}
style={{ width: 100, height: 100 }}
sharedTransitionTag="tag"
/>
</View>
);
}
const RootStack = createNativeStackNavigator({
screens: {
Home: createNativeStackScreen({
screen: HomeScreen,
}),
Details: createNativeStackScreen({
screen: DetailsScreen,
}),
},
});
const Navigation = createStaticNavigation(RootStack);
export default function App() {
return <Navigation />;
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
},
});
import * as React from 'react';
import { View, StyleSheet } from 'react-native';
import { useNavigation, NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { Button } from '@react-navigation/elements';
import Animated from 'react-native-reanimated';
function HomeScreen() {
const navigation = useNavigation();
return (
<View style={styles.container}>
<Button onPress={() => navigation.navigate('Details')}>
Go to Details
</Button>
<Animated.Image
source={{ uri: 'https://picsum.photos/id/39/200' }}
style={{ width: 300, height: 300 }}
sharedTransitionTag="tag"
/>
</View>
);
}
function DetailsScreen() {
const navigation = useNavigation();
return (
<View style={styles.container}>
<Button onPress={() => navigation.goBack()}>Go back</Button>
<Animated.Image
source={{ uri: 'https://picsum.photos/id/39/200' }}
style={{ width: 100, height: 100 }}
sharedTransitionTag="tag"
/>
</View>
);
}
const Stack = createNativeStackNavigator();
function RootStack() {
return (
<Stack.Navigator>
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Details" component={DetailsScreen} />
</Stack.Navigator>
);
}
export default function App() {
return (
<NavigationContainer>
<RootStack />
</NavigationContainer>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
},
});
sharedTransitionTag is a string that has to be unique in the context of a single screen, but has to match elements between screens. This prop allows Reanimated to identify and animate the elements, similarly to the key property, which tells React which element in the list is which.
Customizing the transition
You can customize the transition by passing a custom SharedTransition configuration via the sharedTransitionStyle prop. Apply the same sharedTransitionStyle to the matching element on the target screen.
The default transition animates width, height, originX, originY, transform, backgroundColor, and opacity using withTiming with a 500 ms duration.
Currently customization is more limited due to ongoing development. You can't define fully custom animation functions. Instead, use the SharedTransition builder class to configure duration and spring-based animations:
import { SharedTransition } from 'react-native-reanimated';
// Customize duration and use spring animation
const customTransition = SharedTransition.duration(550).springify();
function HomeScreen() {
return (
<Animated.Image
style={{ width: 300, height: 300 }}
sharedTransitionTag="tag"
sharedTransitionStyle={customTransition}
/>
);
}
Custom transition configuration is not fully finalized and might change in a future release.
Reference
You can find a full Shared Element Transitions reference in the React Native Reanimated documentation.
Limitations
Shared Element Transitions currently have several limitations to be aware of:
- Only the native stack navigator is supported
- Other navigators such as JS stack, drawer, and bottom tabs are not supported
- Transitions with native modals don't work properly on iOS
- The feature must be enabled via the
ENABLE_SHARED_ELEMENT_TRANSITIONSfeature flag - Custom animation functions are not supported; you can only customize duration and use spring-based animations
- Some properties (e.g.,
backgroundColor) are not supported in progress-based transitions (iOS back gesture) - There are performance bottlenecks with transforms being recalculated too eagerly
- On iOS, you may encounter issues with vertical positioning due to header height information propagation
The limitations will be addressed in future Reanimated releases.