Changelogs

New in 3.6

What changed in v3.6 across gestures, snap points, bounds, reveal, and migration compatibility.

Gesture Progress Mode

v3.6 added gestureProgressMode to describe whether gestures should drive screen progress.

In v3.8, gestureProgressMode is deprecated. Current code should use progress for visual progress and transitionProgress when an animation needs transition progress without live gesture movement.

TSX

1options={{
2 gestureEnabled: true,
3 gestureDirection: ["vertical", "horizontal"],
4 gestureProgressMode: "freeform",
5}}

Use "progress-driven" when drag movement should directly move screen progress. Use "freeform" when gesture values should drive custom visuals while transition progress stays controlled by navigation or release.

This deprecated gestureDrivesProgress in favor of a more descriptive option name. The older gestureDrivesProgress: false form still maps to "freeform" for compatibility.

Gesture Tracking

v3.6 adds gestureTracking for screens that need explicit control over whether gestures produce live gesture values.

TSX

1options={{
2 gestureEnabled: false,
3 gestureTracking: "always",
4}}

Use "auto" for the default behavior: gestures track when dismissal is enabled, or when snap points can move without dismissal. Use "always" when a disabled gesture should still drive custom visuals without allowing dismiss. Use "never" when a screen should not track gestures at all, including snap gestures.

gestureTracking: "always" replaces the earlier experimental disabled-gesture tracking option.

Unified Gesture State

Gesture state now covers pan and pinch through the same active.gesture object:

TSX

1screenStyleInterpolator: ({ active }) => {
2 "worklet";
3
4 const gesture = active.gesture.active;
5 const velocity = active.gesture.velocity;
6 const pinchScale = active.gesture.normScale;
7
8 return {
9 content: {
10 style: {
11 opacity: 1 - velocity * 0.2,
12 transform: [{ scale: 1 - Math.abs(pinchScale) * 0.1 }],
13 },
14 },
15 };
16};

Use active.gesture.raw when calculating dynamic gestureSensitivity so sensitivity does not feed back into itself.

Runtime Options in Interpolators

Interpolators can now return an options bucket for values that need to change on the UI thread:

TSX

1screenStyleInterpolator: ({ active }) => {
2 "worklet";
3
4 return {
5 options: {
6 gestureSensitivity: interpolate(
7 Math.abs(active.gesture.raw.normY),
8 [0, 0.25],
9 [1, 0.5],
10 "clamp"
11 ),
12 },
13 };
14};

Screen states also expose resolved options through current.options, active.options, and next?.options, so animation logic can react to base screen options, navigation.setOptions() changes, and runtime overrides.

Dismiss Gesture Persistence

v3.6 changes gesture reset behavior on release. When the gesture resolves to dismissal, the runtime keeps the release gesture values available during the dismiss animation. When the gesture does not dismiss, the runtime resets the gesture values as it settles back.

This lets dismissed screens keep using the release drag and velocity for fling-style animations, while cancelled gestures cleanly return to their resting visual state.

Snap State

Snap state is split into live and target values:

  • current.animatedSnapIndex follows live gesture and animation movement, including fractional values between detents
  • current.snapIndex follows the selected target detent after mount, snapTo(), or gesture release

TSX

1screenStyleInterpolator: ({ current }) => {
2 "worklet";
3
4 return {
5 content: {
6 style: {
7 borderRadius: interpolate(
8 current.animatedSnapIndex,
9 [0, 1, 2],
10 [28, 16, 0],
11 "clamp"
12 ),
13 },
14 },
15 };
16};

For snap release targeting, use gestureSnapVelocityImpact:

TSX

1options={{
2 snapPoints: [0.3, 0.7, 1],
3 gestureSnapVelocityImpact: 0.15,
4}}

Reveal Navigation

v3.6 adds bounds({ id }).navigation.reveal() for masked source-to-container navigation transitions.

Reveal requires a source boundary, a matching destination boundary, and the static navigationMaskEnabled screen option. The helper exposes tuning options such as borderRadius, borderContinuous, maxSensitivity, velocityDepth, disablePointerEventsTillElementTransition, and maskSizingMode.

Read the full setup in Reveal.

Bounds Runtime

Scoped bounds results expose helper methods directly:

TSX

1const hero = bounds({ id: "hero" });
2const measured = hero.getMeasured(current.route.key);
3const link = hero.getLink();
4const initialSource = link?.initialSource;
5const initialDestination = link?.initialDestination;
6const raw = link?.compute({ method: "size", raw: true });

Bounds also gained more stable grouped-link measurement, source/destination snapshot handling, and transition-pair resolution. These are mostly internal, but they make grouped shared boundaries and navigation helpers less likely to flicker, blank, or retarget against stale measurements.

Internally, bounds links are now keyed by screen pair instead of a single global id. A pair owns its links, group active id, initial source, and initial destination, which keeps the same bound id from leaking between unrelated route pairs and makes retargeted grouped transitions deterministic.

Use offset when adding live x/y movement to bounds:

TSX

1bounds({
2 id: "hero",
3 offset: {
4 x: active.gesture.x,
5 y: active.gesture.y,
6 },
7});

Hook Targets

Transition and gesture hooks now prefer depth targets:

TSX

1const selfGesture = useScreenGesture();
2const parentGesture = useScreenGesture({ depth: -1 });
3
4const selfAnimation = useScreenAnimation();
5const parentAnimation = useScreenAnimation({ depth: -1 });
6const childAnimation = useScreenAnimation({ depth: 1 });

useScreenGesture() resolves the current screen and ancestors only. useScreenAnimation() still accepts legacy string targets such as "parent" and "root", but new code should use { depth }.

Migration Checklist

  • For v3.6 projects, replace new gestureDrivesProgress usage with gestureProgressMode. In v3.8 and later, use progress or transitionProgress instead.
  • Use gestureTracking: "always" when disabled gestures should keep producing live gesture values for custom visuals.
  • Prefer active.gesture.active over the older pan-only active.gesture.direction.
  • Use active.gesture.raw when deriving dynamic gestureSensitivity.
  • Return options from screenStyleInterpolator for UI-thread option changes, and read resolved values from active.options when animation logic depends on them.
  • Use gestureSnapVelocityImpact instead of snapVelocityImpact for snap release targeting.
  • Use current.animatedSnapIndex for live snap visuals and current.snapIndex for selected target logic.
  • For v3.6 projects, use current.logicallySettled, active.logicallySettled, or next?.logicallySettled instead of the deprecated top-level logicallySettled alias. In v3.8 and later, use settled.
  • Use scoped bounds helpers such as bounds({ id }).getMeasured() and bounds({ id }).getLink().
  • Use offset instead of the older gestures bounds option.
  • Use getMeasured() instead of the deprecated getSnapshot() alias.
  • Use layer slots such as content, backdrop, surface, and style id keys instead of legacy flat interpolator keys such as contentStyle, backdropStyle, and overlayStyle.

Compatibility Notes

  • gestureDrivesProgress still maps to gestureProgressMode, and both are deprecated in v3.8.
  • gestureTracking is static and is not supported inside interpolator-returned runtime options.
  • expandViaScrollView still maps to sheetScrollGestureBehavior.
  • getSnapshot() still exists as a deprecated alias for getMeasured().
  • active.gesture.direction still exists as a deprecated pan-only alias for active.gesture.active.
  • gestureReleaseVelocityMax remains in the static screen option type as deprecated compatibility surface, but it is not read by runtime transition options.