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

The return object uses a flat style structure. Return 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]) },
      ],
    },
  };
};
Never nest styles like content: { style: { ... } }. Always use the flat format.

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 timing and physics for open/close/expand/collapse animations:
const customAnimation = {
  screenStyleInterpolator: ({ progress }) => {
    // ... interpolation logic
  },
  transitionSpec: {
    open: {
      timing: "spring",
      config: { stiffness: 600, damping: 60, mass: 1 },
    },
    close: {
      timing: "spring",
      config: { stiffness: 600, damping: 60, mass: 1 },
    },
    expand: {
      timing: "spring",
      config: { stiffness: 400, damping: 40, mass: 1 },
    },
    collapse: {
      timing: "spring",
      config: { stiffness: 400, damping: 40, mass: 1 },
    },
  },
};
All animation specs use spring configurations:
  • 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
activeScreenProgress[]All active screens (nested)
inactiveScreenProgress[]All inactive screens
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
closingbooleanScreen is closing
enteringbooleanScreen is entering
animatingbooleanScreen is animated
gestureGestureStateActive gesture data
metaobjectCustom metadata

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