Set up the portal
Mount one portal near the application root.
MagicModalPortal owns the stack rendered by magicModal.show() and the context used by
useMagicModal(). Mount it once after the application content. Native and shared Expo roots place
it inside GestureHandlerRootView. A web-only Next.js shell mounts the portal directly.
import { MagicModalPortal } from "magic-modal";
import { GestureHandlerRootView } from "react-native-gesture-handler";
export default function App() {
return (
<GestureHandlerRootView style={{ flex: 1 }}>
<YourApp />
<MagicModalPortal />
</GestureHandlerRootView>
);
}The gesture root is required on native even if the first modal does not use swipe-to-dismiss. The portal owns the gesture surface, and native gesture handling requires a root view.
Expo Router
The root layout is usually the clearest place for both components:
import { Stack } from "expo-router";
import { MagicModalPortal } from "magic-modal";
import { GestureHandlerRootView } from "react-native-gesture-handler";
export default function RootLayout() {
return (
<GestureHandlerRootView style={{ flex: 1 }}>
<Stack />
<MagicModalPortal />
</GestureHandlerRootView>
);
}Keep the portal outside individual screens. It stays mounted across navigation. A flow can start on one screen and resolve after the active screen changes.
Pick your runtime
The portal stays the same. Its root file and bundler setup depend on the runtime.
Next.js and web
Add the portal to a Client Component and map React Native to React Native Web.
Expo
Use the same root and modal components on iOS, Android, and web.
iOS and Android
Handle the Android back action and iOS native overlays.
Continue by showing your first modal.