Goal
This recipe builds a nested modal flow that can turn drag-to-dismiss on and off per nested screen while the modal still reacts to the user's pull gesture.
Use this pattern when a modal represents a flow the user should intentionally finish or exit, such as onboarding, account setup, checkout, or any multi-step setup. The modal can still acknowledge a pull gesture, but the resisted motion communicates that the flow is not casually dismissible.
Route Shape
TEXT
The parent stack owns the modal presentation. The nested modal stack owns the screens inside that modal. Each nested screen can update the parent modal's gestureEnabled option when it receives focus.
Parent Layout
app/example/_layout.tsx
What The Animation Is Communicating
There are a couple of moving pieces in this interpolator, so it is worth calling out what each one is doing.
The modal starts with gestureEnabled: true, so the first modal screen can be dismissed normally. gestureTracking: "always" stays static on the parent modal route, which means gesture values are still tracked after a nested screen disables dismissal.
gestureSensitivity is what gives the disabled modal its resistance. When current.options.gestureEnabled is false, the interpolator lowers sensitivity as the modal gets closer to its settled state. That makes the pull feel damped instead of linear: the user can tug on the modal, but the modal communicates that it does not want to leave.
gestureReleaseVelocityScale is more about feel than correctness. Setting it to 0 prevents a quick downward swipe from carrying too much release energy into the disabled state. The modal would still avoid dismissal without this, but the resistance can feel too extreme or springy when the user flings hard.
The maxHeight and marginTop values make the modal a real bottom-aligned card instead of a full-height screen that only appears offset. That matters because the nested modal screens use the custom IOSSlide() animation, which adds border radius during the page slide. If the parent modal content stayed full height and only moved with translateY, the rounded bottom edge would sit outside the visible area and the nested slide could look like it is overflowing. Giving the modal an actual height * 0.9 layout box keeps the rounded card shape visible while the nested pages animate inside it.
Launcher Screen
app/example/index.tsx
Nested Modal Stack
app/example/modal/_layout.tsx
The nested stack is wrapped in the rounded modal surface. The b screen uses the regular iOS slide helper so navigation inside the modal stays separate from the parent modal presentation.
Dismissable Screen
app/example/modal/index.tsx
When the first modal screen is focused, it enables dismissal on the parent modal route.
Disabled Screen
app/example/modal/b.tsx
When b is focused, it disables dismissal on the parent modal route. Because the parent route has gestureTracking: "always", the modal can still track the pull and apply the disabled resistance from the parent interpolator.
Closing Thoughts
This recreates the iOS 26 modal look and behavior on iOS and Android.
The parent modal owns the presentation, gesture tracking, and resistance tuning. The nested screens only decide whether the parent modal can dismiss while they are focused. That split keeps the recipe predictable: screen focus updates gestureEnabled, and the parent interpolator turns the resolved option into the enabled or disabled modal feel.
The result is a modal that can be dismissable when it should leave, resistant when it should stay, and responsive to touch the whole time.