A modal in React Native is a real native window, not a repositioned <View>.
That single fact removes a whole class of web problems and introduces a few new ones.
No portal, and no z-index war
On the web, a dialog rendered where it sits in the tree gets clipped by any ancestor with
overflow: hidden and buried under any ancestor that creates a stacking context. That is
what createPortal exists to escape.
React Native's Modal renders outside the parent view hierarchy natively, so it is
always on top. There is no createPortal in React Native and you will not need
one. An overlay that is not a Modal — a toast, say — can simply be positioned
absolutely and rendered last, because rendering order decides what is on top and nothing clips a
sibling the way CSS does.
Building a bottom sheet
Bottom sheets are the native shape for what the web does with a centred dialog or a side drawer. They keep the primary action within thumb reach instead of at the top of the screen:
<Modal
visible={visible}
transparent
animationType="slide"
onRequestClose={onClose}
statusBarTranslucent
>transparent is what lets you draw your own backdrop — without it the modal is an
opaque full-screen window. animationType="slide" gives the platform's own slide
transition for free.
⚠️ onRequestClose is required on Android and ignored on iOS. It
fires on the hardware back button. Omit it and back exits the entire app while a sheet is open — a
bug you will never see if you only test on iOS.
The three things a web modal library gave you
React Native's Modal is deliberately low-level. Bootstrap or Radix handled these;
here you wire them:
<KeyboardAvoidingView
style={styles.fill}
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
>
<Pressable style={styles.backdrop} onPress={onClose} accessibilityLabel="Close" />
<View
style={[styles.panel, { paddingBottom: insets.bottom + theme.spacing.md }]}
testID={testID}
>Tap-the-backdrop-to-close is a Pressable filling the screen behind
the panel. The panel is deliberately not a child of it — nest them and every tap on the
sheet's own content closes the sheet.
The keyboard needs KeyboardAvoidingView here as much as on a
screen, with the same per-platform behavior.
The safe area applies again. A modal is its own window, so being inside a
screen that already applied insets does not help it — it reads them itself. That
paddingBottom is what keeps the sheet's buttons clear of the home indicator.
What is still missing
Focus is not trapped. A screen reader can walk out of the sheet into the screen behind it. React
Native gives you accessibilityViewIsModal on iOS and
importantForAccessibility="no-hide-descendants" on the content behind, and neither is
automatic. The demo app documents this as a known gap rather than pretending otherwise — worth
closing in a production app.
Making it dismissible
<View style={styles.header}>
<Text variant="heading">{title}</Text>
<Pressable
onPress={onClose}
hitSlop={12}
accessibilityRole="button"
accessibilityLabel="Close"
testID="sheet-close"
>An explicit close control matters more on a sheet than on a web dialog, because there is no
Escape key. hitSlop={12} makes a small "✕" glyph a comfortable target without drawing
a large button.
Closing before navigating
A trap worth naming, because the symptom is baffling. When a sheet contains a button that navigates elsewhere, close the sheet first:
function goToCheckout() {
onClose();
router.push('/checkout');
}Navigate while the modal is still mounted and it stays over the new screen, invisible or not, swallowing every tap. The user arrives at a page that appears to be frozen. The demo app's test suite asserts the ordering rather than trusting it.
Toasts need none of this
<View style={[styles.host, { top: insets.top + theme.spacing.sm }]} pointerEvents="none">
{toasts.map((toast) => (
<ToastCard key={toast.id} toast={toast} />
))}
</View>An absolutely positioned View, rendered last inside the provider. No
Modal, no portal — being last in the tree is enough to be on top.
pointerEvents="none" is the important prop: it lets touches pass straight through
the container to whatever is underneath, so a toast never blocks a button while it is visible.
Libraries
@gorhom/bottom-sheet gives you a draggable, snap-pointed sheet with proper gesture
handling, and is what you want if the sheet is a primary interaction. The version above is a hundred
lines and covers the common case — a panel that appears, takes a decision and goes away.
What is next
Animations and the Native Driver — one flag that decides whether an animation is smooth.