Skip to main content
TASIOMIND.DEV — OPERATIONAL▸▸▸FULL STACK DEVELOPER @ GWQ SERVICEPLUS AG▸▸▸FOUNDER — K8SGPT.AI▸▸▸OPEN SOURCE: ACTIVE▸▸▸DISTRIBUTED SYSTEMS / KUBERNETES / AI▸▸▸RUST + GO + PYTHON▸▸▸FIELD TESTED / STATUS — NOMINAL▸▸▸LOCATION: EUROPE/BERLIN▸▸▸TASIOMIND.DEV — OPERATIONAL▸▸▸FULL STACK DEVELOPER @ GWQ SERVICEPLUS AG▸▸▸FOUNDER — K8SGPT.AI▸▸▸OPEN SOURCE: ACTIVE▸▸▸DISTRIBUTED SYSTEMS / KUBERNETES / AI▸▸▸RUST + GO + PYTHON▸▸▸FIELD TESTED / STATUS — NOMINAL▸▸▸LOCATION: EUROPE/BERLIN▸▸▸
react-native

Implementing Shared Element Transitions in React Native

Aland Baban · January 2021 · 6 min read
[ contents ]

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-navigation and react-navigation-shared-element for 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-cli installed globally, or accessible via npx

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.

code
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.

code
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.

code
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.

code
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.

code
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:

code
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:

code
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.

code
// 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.

code
// 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.

Complex Transition

Simply wrap the corresponding elements on both screens and add them to the sharedElements configuration array in DetailScreen.js.

code
// 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.

code
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.

Final Animation

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.