Skip to main content

The Progress Model

All animations in Screen Transitions are driven by a progress value that ranges from 0 to 2:
  • 0 → 1: Screen is entering (becoming visible)
  • 1 → 2: Screen is exiting (becoming hidden)
  • Exactly 1: Screen is fully visible and in focus
When you navigate from Screen A to Screen B:
  • Screen B enters: progress 0 → 1
  • Screen A exits: progress 1 → 2
This allows you to synchronize animations between screens.

Simple Fade Animation

Here’s the simplest custom animation — a fade in and out:
import { interpolate } from "react-native-reanimated";
import Transition from "react-native-screen-transitions";

const fadeAnimation = {
  screenStyleInterpolator: ({ progress }) => {
    "worklet";
    return {
      contentStyle: {
        opacity: interpolate(progress, [0, 1, 2], [0, 1, 0]),
      },
    };
  },
};

<Stack.Screen
  name="Detail"
  component={DetailScreen}
  options={{
    ...fadeAnimation,
  }}
/>
The interpolate function maps progress from [0, 1, 2] to opacity values [0, 1, 0]. At progress 0 (entering), opacity is 0. At progress 1 (visible), opacity is 1. At progress 2 (exiting), opacity is 0.

Slide from Right

Slide a screen in from the right edge:
import { interpolate } from "react-native-reanimated";

const slideFromRight = {
  screenStyleInterpolator: ({ progress, layouts }) => {
    "worklet";
    const screenWidth = layouts.screen.width;

    return {
      contentStyle: {
        transform: [
          {
            translateX: interpolate(
              progress,
              [0, 1, 2],
              [screenWidth, 0, -screenWidth]
            ),
          },
        ],
      },
    };
  },
};
The screen starts at screenWidth (off-screen right), moves to 0 (visible), then slides left to -screenWidth as it exits.

Slide from Bottom

A common mobile pattern for modals:
const slideFromBottom = {
  screenStyleInterpolator: ({ progress, layouts }) => {
    "worklet";
    const screenHeight = layouts.screen.height;

    return {
      contentStyle: {
        transform: [
          {
            translateY: interpolate(
              progress,
              [0, 1, 2],
              [screenHeight, 0, screenHeight]
            ),
          },
        ],
      },
    };
  },
};

Return Format: Flat Styles

In the 3.3 docs, the examples use the flat return shape: contentStyle and backdropStyle at the top level, plus any styleId keys:
screenStyleInterpolator: ({ progress }) => {
  "worklet";
  return {
    contentStyle: {
      opacity: interpolate(progress, [0, 1, 2], [0, 1, 0]),
    },
    backdropStyle: {
      opacity: interpolate(progress, [0, 1, 2], [0, 0.3, 0]),
    },
    // Target specific elements via their styleId
    "avatar": {
      transform: [
        { scale: interpolate(progress, [0, 1, 2], [0.5, 1, 0.5]) },
      ],
    },
  };
};
That flat format is the 3.3 style this page uses.

Targeting Elements with styleId

Use styleId on any component to target it in the interpolator:
import Transition from "react-native-screen-transitions";

function DetailScreen() {
  return (
    <View>
      <Transition.View
        styleId="avatar"
        style={{ width: 60, height: 60 }}
      >
        <Image source={{ uri: "..." }} style={{ width: 60, height: 60 }} />
      </Transition.View>
    </View>
  );
}

const detailOptions = {
  screenStyleInterpolator: ({ progress }) => {
    "worklet";
    return {
      contentStyle: {
        opacity: interpolate(progress, [0, 1, 2], [0, 1, 0]),
      },
      "avatar": {
        transform: [
          { scale: interpolate(progress, [0, 1, 2], [0.5, 1, 1.2]) },
        ],
      },
    };
  },
};

Animation Specs

Define physics for open, close, expand, and collapse animations:
const customAnimation = {
  screenStyleInterpolator: ({ progress }) => {
    // ... interpolation logic
  },
  transitionSpec: {
    open: {
      stiffness: 600,
      damping: 60,
      mass: 1,
    },
    close: {
      stiffness: 600,
      damping: 60,
      mass: 1,
    },
    expand: {
      stiffness: 400,
      damping: 40,
      mass: 1,
    },
    collapse: {
      stiffness: 400,
      damping: 40,
      mass: 1,
    },
  },
};
Each key takes a Reanimated animation config. Spring configs are the most common:
  • stiffness — How aggressively the spring pulls (default 100)
  • damping — How quickly the spring settles (default 10)
  • mass — Weight of the object (default 1)
Higher stiffness = faster acceleration. Higher damping = less bounce.

Interpolator Props Reference

The screenStyleInterpolator function receives a single object with these properties:
PropTypeDescription
progressAnimated Value0→1→2 progress of the current screen
stackProgressAnimated ValueOverall stack progress (0 = first screen, 1 = fully nested)
snapIndexAnimated ValueCurrent snap point index (0-based)
focusedbooleanIs this screen focused?
currentScreenProgressCurrent screen state
previousScreenProgressPrevious screen state
nextScreenProgressNext screen state
activeScreenProgressScreen state currently driving the transition
inactiveScreenProgress | undefinedThe non-driving screen state when available
layouts.screenCurrent screen dimensions
insetsSafeAreaInsetsSafe area insets
boundsfunctionBounds measurement API

Screen State Properties

ScreenProgress objects have these properties:
PropertyTypeDescription
progressAnimated ValueNormalized 0→1→2 progress
closingAnimated flagScreen is closing
enteringAnimated flagScreen is entering
animatingAnimated flagScreen is animating
gestureGestureStateActive gesture data
metaobjectCustom metadata
layoutsobjectScreen measurements
snapIndexAnimated ValueCurrent snap point index

Conditional Logic with meta

Pass metadata to interpolators and check it for conditional behavior:
// In screen options
const screenOptions = {
  screenStyleInterpolator: ({ progress, current }) => {
    "worklet";
    const { meta } = current;
    const isPortrait = meta?.orientation === "portrait";

    return {
      contentStyle: {
        opacity: interpolate(progress, [0, 1, 2], [0, 1, 0]),
      },
    };
  },
};

GestureState Properties

When a user is dragging, the gesture state includes:
PropertyTypeDescription
isDraggingbooleanUser is actively dragging
xAnimated ValueX position
yAnimated ValueY position
normalizedX0-1X as fraction of screen width
normalizedY0-1Y as fraction of screen height
isDismissingbooleanGesture is a dismiss
direction”up” | “down” | “left” | “right”Gesture direction

Advanced: Conditional Animation

Respond to user gestures in real time:
const advancedAnimation = {
  screenStyleInterpolator: ({ progress, current }) => {
    "worklet";
    const { isDragging } = current.gesture;

    // While dragging, use progress directly
    // After release, snap to nearest target
    const opacity = isDragging
      ? interpolate(progress, [0, 1, 2], [0, 1, 0])
      : interpolate(progress, [0, 1, 2], [0, 1, 0]);

    return {
      contentStyle: { opacity },
    };
  },
};

Next Steps