Skip to main content
Transition components are enhanced versions of React Native primitives that integrate with the screen transition system. They enable shared element animations, custom style interpolation, and gesture coordination.

Available Components

All transition components are accessible via the default export:
import Transition from "react-native-screen-transitions";

<Transition.View />
<Transition.Pressable />
<Transition.ScrollView />
<Transition.FlatList />
<Transition.MaskedView />

Transition.View

Animated view component with support for shared element transitions and custom style interpolation.

Basic Usage

import Transition from "react-native-screen-transitions";

<Transition.View style={{ padding: 20 }}>
  <Text>This view can be animated during transitions</Text>
</Transition.View>

Props

sharedBoundTag
string
Marks this component for measurement to enable shared element transitions. Components with the same tag across different screens will animate between each other.
// Screen A:
<Transition.View sharedBoundTag="profile-avatar">
  <Avatar size="small" />
</Transition.View>

// Screen B:
<Transition.View sharedBoundTag="profile-avatar">
  <Avatar size="large" />
</Transition.View>
styleId
string
Connects this component to custom animated styles defined in screenStyleInterpolator. When you return custom styles from your interpolator with a matching key, those styles will be applied to this component during transitions.
// In your component:
<Transition.View styleId="hero-image">
  <Image source={...} />
</Transition.View>

// In your screenStyleInterpolator:
screenStyleInterpolator: ({ progress }) => {
  "worklet";
  return {
    'hero-image': {
      opacity: interpolate(progress, [0, 1], [0, 1]),
      transform: [{ scale: interpolate(progress, [0, 1], [0.8, 1]) }]
    }
  };
}
remeasureOnFocus
boolean
default:"false"
Re-measures this component when the screen regains focus and updates any matching shared-bound source link in place.Useful when layout can change while unfocused (for example, programmatic ScrollView/FlatList scrolling triggered from another screen).
Transition.View accepts all standard View props and passes them to the underlying animated component.

Transition.Pressable

Pressable component that measures bounds when pressed, enabling shared element transitions from interactive elements.

Basic Usage

import Transition from "react-native-screen-transitions";
import { router } from "expo-router";

<Transition.Pressable
  sharedBoundTag="avatar"
  onPress={() => router.push("/profile")}
>
  <Image source={avatar} style={{ width: 50, height: 50 }} />
</Transition.Pressable>

How It Works

  1. When pressed, Transition.Pressable measures its layout bounds
  2. Stores the bounds with the specified sharedBoundTag
  3. On the destination screen, a Transition.View with the same tag retrieves these bounds
  4. The interpolator animates between the two bounds

Example: Instagram-Style Image Transition

// List Screen
import Transition from "react-native-screen-transitions";
import { router } from "expo-router";

export default function ListScreen() {
  return (
    <Transition.Pressable
      sharedBoundTag="post-image"
      onPress={() => {
        router.push({
          pathname: "/post",
          params: { sharedBoundTag: "post-image" },
        });
      }}
    >
      <Image source={...} style={{ width: 200, height: 200 }} />
    </Transition.Pressable>
  );
}

// Post Screen
import Transition from "react-native-screen-transitions";
import { useLocalSearchParams } from "expo-router";

export default function PostScreen() {
  const { sharedBoundTag } = useLocalSearchParams<{ sharedBoundTag: string }>();

  return (
    <Transition.View sharedBoundTag={sharedBoundTag}>
      <Image source={...} style={{ width: 400, height: 400 }} />
    </Transition.View>
  );
}

// _layout.tsx
import Transition from "react-native-screen-transitions";

<Stack.Screen
  name="post"
  options={({ route }) => ({
    ...Transition.Presets.SharedIGImage({
      sharedBoundTag: route.params?.sharedBoundTag ?? "",
    }),
  })}
/>

Props

Inherits all props from Transition.View plus all standard Pressable props.

Transition.ScrollView

ScrollView with gesture coordination that works correctly with screen dismiss gestures.

Basic Usage

import Transition from "react-native-screen-transitions";

<Transition.ScrollView>
  <Text>Scrollable content</Text>
  {/* More content */}
</Transition.ScrollView>

Gesture Coordination

When used inside a screen with dismiss gestures, Transition.ScrollView automatically coordinates with the screen gesture:
  • Vertical dismiss: Only activates when scrolled to top
  • Vertical-inverted dismiss: Only activates when scrolled to bottom
  • Horizontal dismiss: Only activates at left/right scroll edges

With Snap Points (Bottom Sheets)

// Screen with snap points
<Stack.Screen
  name="Sheet"
  options={{
    gestureEnabled: true,
    gestureDirection: "vertical",
    snapPoints: [0.5, 1],
    expandViaScrollView: true, // Allow expansion from scroll
    ...Transition.Presets.SlideFromBottom(),
  }}
/>

// In the sheet component
function SheetScreen() {
  return (
    <Transition.ScrollView>
      {/* Content */}
    </Transition.ScrollView>
  );
}

Scroll Behavior with expandViaScrollView

  • At scroll top, swipe up expands the sheet
  • At scroll top, swipe down collapses (or dismisses if at minimum)
  • Scrolled into content: Normal scroll behavior

Props

Inherits all props from Transition.View plus all standard ScrollView props.
Always use Transition.ScrollView instead of React Native’s ScrollView when inside screens with gesture-enabled transitions. Regular ScrollView will not coordinate properly with dismiss gestures.

Transition.FlatList

FlatList with gesture coordination, following the same principles as Transition.ScrollView.

Basic Usage

import Transition from "react-native-screen-transitions";

<Transition.FlatList
  data={items}
  renderItem={({ item }) => <ItemComponent item={item} />}
  keyExtractor={(item) => item.id}
/>

Example: Scrollable Bottom Sheet

import Transition from "react-native-screen-transitions";

function BottomSheetWithList() {
  return (
    <View style={{ flex: 1 }}>
      <View style={{ padding: 20 }}>
        <Text>Header content</Text>
      </View>
      
      <Transition.FlatList
        data={items}
        renderItem={({ item }) => <ListItem item={item} />}
        keyExtractor={(item) => item.id}
      />
    </View>
  );
}

Props

Inherits all props from Transition.View plus all standard FlatList props.

Transition.MaskedView

Special component that creates reveal effects for shared element transitions. Required for Instagram and Apple Music-style presets.
Requires native code. Will not work in Expo Go. You must install @react-native-masked-view/masked-view and rebuild your app.

Installation

npx expo install @react-native-masked-view/masked-view
npx expo prebuild

Basic Usage

import Transition from "react-native-screen-transitions";

export default function DetailScreen() {
  return (
    <Transition.MaskedView style={{ flex: 1, backgroundColor: "#121212" }}>
      <Transition.View
        sharedBoundTag="album-art"
        style={{
          backgroundColor: "#1DB954",
          width: 400,
          height: 400,
          alignSelf: "center",
          borderRadius: 12,
        }}
      />
      {/* Additional screen content */}
    </Transition.MaskedView>
  );
}

How It Works

  1. Transition.Pressable on source screen measures bounds on press
  2. Transition.View on destination registers as the target for that tag
  3. Transition.MaskedView clips content to the animating shared element bounds
  4. The preset interpolates position, size, and mask for a seamless expand/collapse effect

Complete Example with Apple Music Preset

// Home Screen
import { router } from "expo-router";
import Transition from "react-native-screen-transitions";

export default function HomeScreen() {
  return (
    <Transition.Pressable
      sharedBoundTag="album-art"
      style={{
        width: 200,
        height: 200,
        backgroundColor: "#1DB954",
        borderRadius: 12,
      }}
      onPress={() => {
        router.push({
          pathname: "/player",
          params: { sharedBoundTag: "album-art" },
        });
      }}
    />
  );
}

// Player Screen
import { useLocalSearchParams } from "expo-router";
import Transition from "react-native-screen-transitions";

export default function PlayerScreen() {
  const { sharedBoundTag } = useLocalSearchParams<{ sharedBoundTag: string }>();

  return (
    <Transition.MaskedView style={{ flex: 1, backgroundColor: "#121212" }}>
      <Transition.View
        sharedBoundTag={sharedBoundTag}
        style={{
          backgroundColor: "#1DB954",
          width: 400,
          height: 400,
          alignSelf: "center",
          borderRadius: 12,
        }}
      />
      {/* Player controls and track info */}
    </Transition.MaskedView>
  );
}

// _layout.tsx
import Transition from "react-native-screen-transitions";

<Stack.Screen
  name="player"
  options={({ route }) => ({
    ...Transition.Presets.SharedAppleMusic({
      sharedBoundTag: route.params?.sharedBoundTag ?? "",
    }),
  })}
/>

Props

style
StyleProp<ViewStyle>
Style for the container. Typically includes flex: 1 and a backgroundColor for the revealed content area.
children
React.ReactNode
Content to render inside the masked view. Typically includes a Transition.View with sharedBoundTag matching the source element.

Custom Transition Component

You can create your own transition-aware components using the factory function:
import Transition from "react-native-screen-transitions";
import { TextInput } from "react-native";

const TransitionTextInput = Transition.createTransitionAwareComponent(TextInput);

// Use it like any other transition component
<TransitionTextInput
  styleId="search-input"
  placeholder="Search..."
/>

For Scrollable Components

import { SectionList } from "react-native";
import Transition from "react-native-screen-transitions";

const TransitionSectionList = Transition.createTransitionAwareComponent(
  SectionList,
  { isScrollable: true }
);

Best Practices

Use sharedBoundTag for Transitions

When animating elements between screens, always use sharedBoundTag on both source and destination. This enables accurate measurement and smooth transitions.

Use styleId for Custom Animations

For elements that need custom animations beyond the screen-level interpolator, use styleId to target specific components without affecting others.

Always Use Transition Scrollables

Replace ScrollView and FlatList with Transition.ScrollView and Transition.FlatList when inside gesture-enabled screens to ensure proper gesture coordination.

Install MaskedView for Premium Transitions

To use Instagram and Apple Music-style transitions, install @react-native-masked-view/masked-view and rebuild your app. These presets won’t work without it.