Guides

Caveats & Trade-offs

What you gain, what you give up, and which rough edges still matter in v3.

Blank Stack vs Native Stack

The main trade-off in this library is not features versus no features. It is blank stack versus native stack.

Native Stack Trade-offs

Native stack transition support is a compatibility layer.

Under the hood, transitioned native-stack screens are switched into containedTransparentModal with native animation disabled, and the transition visuals are driven from the library on top of that.

That works, but it comes with real trade-offs:

  • visually, a background screen may look tappable while still being blocked by the transparent modal layer
  • dismissal can feel worse than blank stack because native removal and JS state synchronization are coordinated separately
  • platform-specific edge cases are more likely here than on blank stack

Use native stack when you need native-stack integration. Do not choose it because you expect it to be the best place for advanced custom transitions.

Blank Stack Trade-offs

Blank stack gives you the most features and the most control.

It is the default recommendation for:

  • custom interpolators
  • gestures
  • snap sheets
  • overlays
  • bounds transitions
  • navigation zoom
  • embedded flows

The trade-off is that blank stack is still a JavaScript navigator. When people compare performance, the fair comparison is a JS stack, not a fully native presentation stack.

In practice, blank stack is where the library works best. Just keep the runtime model in mind when you design ambitious transitions.

Internal release-build testing has shown that simple settled transitions, such as an opaque page push/pop loop or a settled modal sheet loop, can be competitive with the baseline React Navigation JS stack on tested devices.

That does not mean every benchmark will show blank stack as faster. More callback-sensitive push/pop measurements can differ because transition completion is coordinated differently, even when the visual motion feels just as smooth or smoother in practice.

Choose blank stack because you want the richer transition model, gesture control, and motion flexibility. Do not choose it because you expect every timing benchmark to come out lower.

Deep Linking

Deep linking is supported.

These navigators still sit on top of React Navigation, so normal deep-linking flows are very much possible. The caveat is that the more complex the transition choreography becomes, the more opportunities there are for a deep link to land in a visually bad or unexpected transition state.

If you hit a deep-link-specific bug with a complex animation flow, open an issue on GitHub with a reproduction.

Android Native Screens

Blank stack supports nativeScreens={false} because native screen primitives are not always the best fit for every flow.

Before v3.7, Android native screens had a practical dismissal limitation. Fragment presentation could keep the outgoing screen in front until the close fully finished, so content behind it could look interactive while still missing touch events.

v3.7 moves blank-stack presentation through a JS-managed activity layer. Closing scenes stay mounted long enough to finish their exit animation, but they are marked non-interactive while the visible scene below can receive touches as soon as it owns the interaction again.

That old Android fragment touch caveat is no longer a reason to opt out of native screens.

You can still opt out when your layout deliberately needs regular React Native view layering:

TSX

1const Stack = createBlankStackNavigator();
2
3<Stack.Navigator nativeScreens={false}>
4 {/* screens */}
5</Stack.Navigator>

That renders blank stack with regular views instead of react-native-screens.

First Screen Does Not Animate by Default

By default, the first screen in a stack does not animate from its closed state on initial mount. It starts already settled.

That is intentional. Most apps do not want the initial route animating in on first render.

If you do want that behavior, there is an experimental escape hatch:

TSX

1<Stack.Screen
2 name="Home"
3 component={HomeScreen}
4 options={{
5 experimental_animateOnInitialMount: true,
6 }}
7/>

experimental_animateOnInitialMount is experimental and may change.

Masking Dependency

navigationMaskEnabled still depends on @react-native-masked-view/masked-view.

Without that dependency, the app still runs, but navigation masking does not activate.

Live Boundary Handoff

Transition.Boundary handoff is experimental in v3.9.

The feature moves one live payload between matching boundary hosts. It's useful for video players, maps, and other stateful native content, but it also makes platform view lifecycle and compositor behavior observable.

In particular, Android video surfaces have a trade-off:

  • SurfaceView is efficient, but it may render outside normal React Native clipping, transforms, border radii, or portal stacking.
  • TextureView participates in the regular view hierarchy, but it can briefly flicker when native content moves between handoff hosts.

For Expo Video content that must clip or animate with a boundary, prefer:

TSX

1<VideoView
2 player={player}
3 surfaceType="textureView"
4 style={StyleSheet.absoluteFill}
5/>

Test live handoff on physical devices for every platform you support. Ordinary bounds transitions and escapeClipping do not reparent a live payload and are not covered by this experimental warning.

Expo SDK 56

Expo SDK 56 changes Expo Router's navigator internals. This package currently expects the React Navigation imports used by earlier Expo Router integrations.

Because of that, SDK 56 is not supported without changing this package's internals to use Expo Router imports in the adapter layer. If you are on SDK 56, treat Expo Router support as unavailable for now.

Compatibility

The current peer-version floor is:

  • @react-navigation/native >= 6.0.0
  • @react-navigation/native-stack >= 7.0.0 if you use native stack
  • react-native-reanimated >= 3.16.0 or 4.x
  • react-native-gesture-handler >= 2.16.1
  • react-native-screens >= 4.4.0

If your environment is older than that, debug compatibility before you debug transitions.