Implementing Shared Element Transitions in React Native
Note: This tutorial was published in 2021. The React Navigation ecosystem evolves rapidly. While the core concepts of shared element transitions remain similar, the specific libraries, dependencies, and configuration steps may have changed. Always refer to the latest official documentation for
react-navigationandreact-navigation-shared-elementfor the most up-to-date installation instructions.
In mobile application design, transitions between screens play a crucial role in providing design continuity and a seamless user experience. A particularly effective technique is the shared element transition, where a common element is smoothly animated from one screen to another during navigation, maintaining the user's focus on the content.
This tutorial provides a comprehensive guide for React Native developers on how to implement shared element transitions using the react-navigation-shared-element library in conjunction with React Navigation.
The complete source code is available at this GitHub repository.
What are Shared Element Transitions?
Shared element transitions determine how UI elements are animated between two screens during navigation. Instead of standard enter/exit animations where views are treated independently, this technique identifies common elements and creates a seamless visual connection, making the transition feel more natural and intuitive.
Prerequisites
Before you begin, ensure you have the following installed on your local environment:
- Node.js (version >= 12.x.x)
- A package manager such as npm or yarn
expo-cliinstalled globally, or accessible vianpx
This tutorial uses an iOS simulator for demonstrations, but the code will run on Android as well.
Installation and Setup
First, create a new React Native project using expo-cli and install the necessary dependencies for navigation and shared element transitions.
npx expo init shared-element-transitions
cd shared-element-transitions
# Install React Navigation and its dependencies
yarn add @react-navigation/native
expo install react-native-gesture-handler react-native-reanimated react-native-screens react-native-safe-area-context @react-native-community/masked-view
# Install shared element dependencies
yarn add react-native-shared-element react-navigation-shared-element@next
# Optional: For animations
yarn add react-native-animatable
After installation, run the app to ensure the setup is correct.
yarn start
# Press 'i' for iOS simulator or 'a' for Android emulator
You should see the default Expo splash screen.
Creating the Home Screen
The example application will feature a transition from a home screen with a list of items to a detail screen for a selected item.
Create a config/data.js file with some mock data for the list.
export const data = [
{
id: '1',
title: 'Manarola, Italy',
description: 'The Cliffs of Cinque Terre',
image_url: '...',
iconName: 'location-pin'
},
// ... more items
];
Next, create screens/HomeScreen.js. This component will display a scrollable list of tappable items, each showing an image and some text.
import React from 'react';
import { ScrollView, Text, View, TouchableOpacity, Image, Dimensions } from 'react-native';
import { StatusBar } from 'expo-status-bar';
import { data } from '../config/data';
const { width } = Dimensions.get('screen');
const ITEM_WIDTH = width * 0.9;
const ITEM_HEIGHT = ITEM_WIDTH * 0.9;
export default function HomeScreen({ navigation }) {
return (
<View style={{ flex: 1, backgroundColor: '#0f0f0f' }}>
<StatusBar hidden />
{/* Header */}
<View style={{ marginTop: 50, /* ... */ }}>
<Text style={{ color: '#fff', fontSize: 32, fontWeight: '600' }}>Today</Text>
</View>
{/* Scrollable Content */}
<ScrollView>
{data.map(item => (
<TouchableOpacity
key={item.id}
activeOpacity={0.8}
style={{ marginBottom: 14 }}
onPress={() => navigation.navigate('DetailScreen', { item })}
>
<Image
style={{ borderRadius: 14, width: ITEM_WIDTH, height: ITEM_HEIGHT }}
source={{ uri: item.image_url }}
/>
{/* ... overlay text and icon */}
</TouchableOpacity>
))}
</ScrollView>
</View>
);
}
Creating the Detail Screen
The screens/DetailScreen.js will display the full details of a selected item. It receives the item data via route parameters.
import React from 'react';
import { View, ScrollView, Image, Text, Dimensions } from 'react-native';
const { height } = Dimensions.get('window');
const ITEM_HEIGHT = height * 0.5;
const DetailScreen = ({ route }) => {
const { item } = route.params;
return (
<View style={{ flex: 1, backgroundColor: '#0f0f0f' }}>
<Image
source={{ uri: item.image_url }}
style={{ width: '100%', height: ITEM_HEIGHT, /* ... */ }}
/>
{/* ... other details and dummy text */}
</View>
);
};
export default DetailScreen;
Setting up Navigation
The navigation flow will be managed by createSharedElementStackNavigator. This special stack navigator from react-navigation-shared-element wraps each route to detect shared elements and orchestrate transitions.
Create navigation/RootNavigator.js:
import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createSharedElementStackNavigator } from 'react-navigation-shared-element';
import HomeScreen from '../screens/HomeScreen';
import DetailScreen from '../screens/DetailScreen';
const Stack = createSharedElementStackNavigator();
export default function RootNavigator() {
return (
<NavigationContainer>
<Stack.Navigator headerMode="none" initialRouteName="HomeScreen">
<Stack.Screen name="HomeScreen" component={HomeScreen} />
<Stack.Screen name="DetailScreen" component={DetailScreen} />
</Stack.Navigator>
</NavigationContainer>
);
}
Finally, render this RootNavigator in your main App.js file.
Mapping Shared Elements
To enable the transition, wrap the elements you want to share on both screens with the <SharedElement> component from react-navigation-shared-element. Each shared element must be given a unique id that is identical on both the source and destination screens.
In HomeScreen.js, wrap the Image component:
import { SharedElement } from 'react-navigation-shared-element';
// ...
<SharedElement id={`item.${item.id}.image_url`}>
<Image
style={{ borderRadius: 14, width: ITEM_WIDTH, height: ITEM_HEIGHT }}
source={{ uri: item.image_url }}
/>
</SharedElement>
Do the same for the corresponding Image in DetailScreen.js, using the same id format.
Next, define the transition configuration on the DetailScreen component. This static sharedElements function tells the navigator which elements to transition for a given route.
// At the end of DetailScreen.js
DetailScreen.sharedElements = route => {
const { item } = route.params;
return [
{
id: `item.${item.id}.image_url`,
animation: 'move',
resize: 'clip'
}
];
};
The animation property (move, fade) and resize property (clip, stretch) control the transition's appearance.
To customize the screen transition itself (e.g., to use a cross-fade instead of a slide), you can provide a cardStyleInterpolator in the stack navigator options.
// In RootNavigator.js
const options = {
cardStyleInterpolator: ({ current: { progress } }) => ({
cardStyle: {
opacity: progress
}
})
};
// ...
<Stack.Screen name="DetailScreen" component={DetailScreen} options={options} />
Animating Other Shared Elements
You can wrap multiple elements—like text and icons—in <SharedElement> tags to create more complex, coordinated transitions.

Simply wrap the corresponding elements on both screens and add them to the sharedElements configuration array in DetailScreen.js.
// In DetailScreen.sharedElements
return [
// ... image config
{ id: `item.${item.id}.title`, animation: 'fade' },
{ id: `item.${item.id}.description`, animation: 'fade' },
{ id: `item.${item.id}.iconName`, animation: 'move' }
];
Animating Non-Shared Elements
For elements that only exist on one screen, like a close button on the detail screen, you can use animation libraries like react-native-animatable to coordinate their appearance with the transition.
In DetailScreen.js, wrap the close button in an <Animatable.View> and use its props to control the animation timing.
import * as Animatable from 'react-native-animatable';
const DetailScreen = ({ navigation, route }) => {
const buttonRef = React.useRef();
return (
<View>
{/* ... Shared elements */}
<Animatable.View
ref={buttonRef}
animation="fadeIn"
duration={600}
delay={300}
style={/* ... styles for close button container */}
>
<MaterialCommunityIcons
name="close"
// ...
onPress={() => {
buttonRef.current.fadeOut(100).then(() => {
navigation.goBack();
});
}}
/>
</Animatable.View>
</View>
);
};
This creates a smooth fade-in effect for the button and a fade-out effect when it is pressed to navigate back.

Conclusion
Shared element transitions are a powerful tool for creating polished and intuitive user interfaces in React Native. By leveraging libraries like react-navigation-shared-element, developers can build seamless navigational experiences that guide the user's focus and provide a sense of continuity between screens.