Core Concepts

Custom Animations

Write screen interpolators against the v3 transition model: per-screen state, derived helpers, runtime options, and slot output.

Where the Interpolator Lives

Custom animations are created inside screenStyleInterpolator, where each screen returns the animated styles for its built-in layers and any styleId targets it owns.

TSX

1import { interpolate } from "react-native-reanimated";
2import { createBlankStackNavigator } from "react-native-screen-transitions/react-navigation";
3
4const Stack = createBlankStackNavigator();
5
6<Stack.Screen
7 name="Detail"
8 component={DetailScreen}
9 options={{
10 gestureEnabled: true,
11 gestureDirection: "horizontal",
12 screenStyleInterpolator: ({ progress, current }) => {
13 "worklet";
14 const width = current.layouts.screen.width;
15
16 return {
17 content: {
18 style: {
19 transform: [
20 {
21 translateX: interpolate(progress, [0, 1, 2], [width, 0, -width * 0.3]),
22 },
23 ],
24 },
25 },
26 };
27 },
28 }}
29/>

Derived Helpers

The top-level helper props are derived from the current screen relationship:

TSX

1const progress = current.progress + (next?.progress ?? 0);
2const focused = !next;
3const active = focused ? current : (next ?? current);
4const inactive = focused ? previous : current;

Use the top-level progress value when the whole stack relationship should drive the animation. Use current.progress, previous?.progress, or next?.progress when one specific screen should drive it.

progress includes live gesture movement. Use current.transitionProgress when you need the committed transition or snap progress without the active gesture.

In practice:

  • current.progress animates 0 -> 1 when the current screen enters and 1 -> 0 when it exits
  • top-level progress is often 0 -> 1 for the focused entering screen
  • top-level progress is often 1 -> 2 for the screen underneath while a next screen is covering it
  • when that top screen dismisses, those values move back toward the settled state

Interpolator Props

PropMeaning
currentThe screen whose options.screenStyleInterpolator is being evaluated
previousThe screen immediately below current, when one exists
nextThe screen immediately above current, when one exists
activeThe transition driver: current when focused, otherwise next
inactiveThe non-driver: previous when focused, otherwise current
progressDerived helper from current.progress + (next?.progress ?? 0)
stackProgressAccumulated progress from this screen upward through the stack
focusedtrue when there is no next screen above current
boundsBounds accessor for shared-element and navigation zoom helpers
insetsSafe-area insets
layoutsLayout measurements for the current screen

Each screen state object contains the same fields:

FieldMeaning
progressThis screen's own progress value
transitionProgressThis screen's transition or snap progress without live gesture movement
closing1 while this screen is dismissing
entering1 while this screen is opening
willAnimateOne-frame pre-animation handoff signal
animating1 while animation or gesture motion is active
settled1 when the screen is idle and not dismissing
gestureLive gesture values for this screen
metaThe value from options.meta
optionsRuntime transition options after base screen options and interpolator overrides are resolved
routeThe route object for this screen
layouts.screenScreen container size
layouts.contentMeasured content size when available
layouts.scrollMetadata from transition-aware scrollables
animatedSnapIndexLive snap index during gestures and animations, including fractional values between detents
snapIndexCurrent target snap index, updated by mount, snapTo(), and gesture release

Use current.animatedSnapIndex when the visual state should follow the user's finger. Use current.snapIndex when the visual state should follow the selected target detent.

logicallySettled still exists as a deprecated alias for settled. New code should read settled.

Gesture Values

Each screen state's gesture object includes live pan, pinch, rotation, and handoff values:

FieldMeaning
x, yLive pan translation after gestureSensitivity
normX, normYNormalized pan translation after gestureSensitivity
scale, normScaleLive pinch scale values after gestureSensitivity
focalX, focalYPinch focal point in screen coordinates
pinchOriginX, pinchOriginYScreen-coordinate focal point captured when the pinch activates
rotationTwo-finger rotation in radians
rawPhysical pan, pinch, and rotation values before gestureSensitivity
handoffRelease-time gesture snapshot for dismiss handoff animations
activeThe active gesture: a resolved pan direction, pinch-in, pinch-out, or null
dragging, dismissingGesture state flags

Prefer gesture.active for new code. The older pan-only gesture.direction field still exists for compatibility.

focalX and focalY can move with the fingers. Use pinchOriginX and pinchOriginY when a transform should stay anchored to the point where the pinch began.

Live gesture values reset after release. If the dismiss animation needs the last drag, pinch, velocity, focal point, pinch origin, or rotation values, read from gesture.handoff.

Scroll Metadata

Transition-aware scrollables publish scroll state on current.layouts.scroll:

TSX

1screenStyleInterpolator: ({ current }) => {
2 "worklet";
3
4 const y = current.layouts.scroll?.vertical?.offset ?? 0;
5
6 return {
7 content: {
8 style: {
9 transform: [{ translateY: -Math.min(y, 24) }],
10 },
11 },
12 };
13};

For nested same-axis scrollables, the outermost scrollable owns that axis. Cross-axis nested scrollables can each publish their own axis.

Runtime Options From An Interpolator

screenStyleInterpolator can return a reserved options key alongside layer styles. These values are consumed by the transition runtime and stripped before style slots are applied.

TSX

1screenStyleInterpolator: ({ active, progress }) => {
2 "worklet";
3
4 const gestureSensitivity = interpolate(
5 Math.abs(active.gesture.raw.normY),
6 [0, 0.25],
7 [1, 0.4],
8 "clamp"
9 );
10
11 return {
12 options: {
13 gestureSensitivity,
14 gestureSnapLocked: active.options.gestureEnabled === false,
15 },
16 content: {
17 style: {
18 opacity: interpolate(progress, [0, 1], [0, 1]),
19 },
20 },
21 };
22};

Use this for values that must change per frame on the UI thread, such as dynamic gestureSensitivity. Use navigation.setOptions() for React-side option changes such as toggling gestureEnabled or switching gestureDirection while a screen stays mounted. Structural options such as navigationMaskEnabled and gestureTracking must stay on the static screen options.

Supported runtime option keys are:

  • gestureEnabled
  • gestureDirection
  • gestureSensitivity
  • gestureVelocityImpact
  • gestureSnapVelocityImpact
  • gestureReleaseVelocityScale
  • gestureSnapLocked
  • sheetScrollGestureBehavior
  • backdropBehavior

Deprecated compatibility keys are still accepted:

  • gestureProgressMode
  • gestureDrivesProgress
  • gestureActivationArea
  • gestureResponseDistance

Returning Styles

Return built-in layer slots or any custom key that matches a styleId on a transition-aware component:

TSX

1return {
2 content: {
3 style: { opacity: 1 },
4 },
5 backdrop: {
6 style: { opacity: 0.4 },
7 },
8 surface: {
9 style: { borderRadius: 24 },
10 props: { pointerEvents: "none" },
11 },
12 "hero-title": {
13 style: { transform: [{ translateY: -8 }] },
14 },
15};

Reserved layer slots are:

  • content
  • backdrop
  • surface

Any other key is treated as a styleId target. Each slot accepts either shorthand style output or the explicit { style, props } form.

Return null, undefined, or {} when a frame should apply no transition styling.

Style Reset Behavior

The style resolver tracks the keys each slot emitted previously. When a later frame drops a transition-owned key, the library emits a concrete identity reset for that key so Reanimated does not keep stale animated values alive.

This applies to visual transition props such as transform, translateX, translateY, scale, opacity, zIndex, elevation, border radius/color, shadow props, backgroundColor, overflow, and pointerEvents.

Layout-owning props such as width, height, margin, padding, top, left, flex, and aspectRatio are not reset to zero. If you animate layout-owned props, keep returning the value while you need it, then let your own component styles own the settled layout.