Two ways to create modals in Expo Router
Modals can be created using React Native's Modal component or Expo Router's file-based syntax. React Native's Modal component is best for standalone interactions and temporary alerts. Expo Router's modal screen is used for complex interactions that need to be part of the navigation system, such as multi-step forms.
Create modal route with presentation option
To implement a modal route in Expo Router, create a screen file (such as modal.tsx) inside the app directory. In the root layout file, add the modal route to the Stack and set the presentation option to 'modal' on the Stack.Screen component.
Modal presentation behavior across platforms
On Android, the modal slides on top of the current screen and is dismissed using the back button. On iOS, the modal slides from the bottom and is dismissed by swiping down from the top. On web, the modal is presented as a separate route and dismissal must be handled manually using router.canGoBack().
Check modal presentation context on web
Use router.canGoBack() to detect if a modal is presented as an overlay (returns true) or as a standalone full screen (returns false). This is useful on web where modals can be reloaded or navigated to directly, requiring conditional UI rendering.
Configure iOS status bar in modal
By default on iOS, modals have a dark background that hides the status bar. Use the Platform API to check for iOS and then use the StatusBar component from expo-status-bar to change the status bar appearance inside the modal screen.
Anchor modals for deep-linking
When deep-linking to modal routes in nested stacks, export unstable_settings from the stack's layout file with an anchor property (e.g., anchor: 'index'). The anchor serves as the base route that stays in the background when presenting the modal, maintaining correct navigation context.
Form sheet presentation mode
Form sheet presents a modal as a draggable bottom sheet with configurable snap positions called detents. Set presentation to 'formSheet' on the Stack.Screen options to enable this behavior.
Configure sheet detents with numeric values
Detents can be specified as a numeric array (number[]) with values between 0 and 1 representing fractions of screen height. For example, [0.25, 0.5, 1] creates snap points at 25%, 50%, and full screen. Values must be sorted in ascending order. Android supports maximum 3 detents; iOS accepts any number.
Configure sheet detents with fitToContents
Use 'fitToContents' as a sheetAllowedDetents option to make the sheet automatically size based on its content. When using this option, provide explicit content sizing since flex: 1 is not supported because the sheet needs to know the content's actual size.
Form sheet options table
Form sheet Stack.Screen options:
- sheetInitialDetentIndex (number | 'last'): Index of the detent where the sheet opens (default: 0)
- sheetGrabberVisible (boolean): Shows a grabber handle at the top of the sheet (iOS only)
- sheetCornerRadius (number): Corner radius of the sheet in pixels
- sheetLargestUndimmedDetentIndex (number | 'none' | 'last'): Largest detent index that keeps the background undimmed
Android form sheet limitations
On Android, form sheets do not support native stack headers and nested stack navigators. Options such as headerShown, title, and header buttons will not render inside the sheet. If a form sheet needs a title or actions, render them as part of the sheet content instead.
Sheet footer with unstable_sheetFooter
Use the experimental unstable_sheetFooter option (Android-only) to add a footer that stays visible at all detent positions. It accepts a React component that renders at the bottom of the sheet.
Using flex: 1 with form sheet detents
In SDK 55 and later, flex: 1 works correctly on iOS when using custom numeric detents to fill available space within the sheet. This does not work with fitToContents where explicit content sizing must be provided.
Presentation mode options
Available presentation options for Stack.Screen: card (default push animation), modal (full screen modal with nested stack support), transparentModal (modal with previous screen visible behind), containedModal (Android: fallback to modal; iOS: UIModalPresentationCurrentContext), containedTransparentModal (Android: fallback to transparentModal; iOS: UIModalPresentationOverCurrentContext), fullScreenModal (Android: fallback to modal; iOS: UIModalPresentationFullScreen), formSheet (bottom sheet with detents).