Portal
Render content into a shared overlay layer that sits above the rest of the app. Portal is the foundation primitive for dialogs, snackbars, menus, tooltips, and bottom sheets — anything that needs to escape its parent's layout and z-order.
Setup
Wrap your app root with PortalHost once. Every <Portal> rendered anywhere in the tree below will mount into this host's overlay layer.
import { PortalHost, Typography } from '@onlynative/components'
import { ThemeProvider } from '@onlynative/core'
import { SafeAreaProvider } from 'react-native-safe-area-context'
import { View } from 'react-native'
export default function App() {
return (
<SafeAreaProvider>
<ThemeProvider>
<PortalHost>
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Typography variant="bodyLarge">App content lives here.</Typography>
</View>
</PortalHost>
</ThemeProvider>
</SafeAreaProvider>
)
}
Usage
import { Button, Portal, PortalHost, Typography } from '@onlynative/components'
import { ThemeProvider } from '@onlynative/core'
import { SafeAreaProvider } from 'react-native-safe-area-context'
import { useState } from 'react'
import { Pressable, View } from 'react-native'
function Screen() {
const [open, setOpen] = useState(false)
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Button variant="filled" onPress={() => setOpen(true)}>Open overlay</Button>
{open ? (
<Portal>
<Pressable
onPress={() => setOpen(false)}
style={{ position: 'absolute', inset: 0, backgroundColor: 'rgba(0,0,0,0.4)', alignItems: 'center', justifyContent: 'center' }}
>
<View style={{ backgroundColor: 'white', padding: 24, borderRadius: 12 }}>
<Typography variant="titleMedium">Hello from a Portal</Typography>
<Typography variant="bodyMedium">Tap outside to close.</Typography>
</View>
</Pressable>
</Portal>
) : null}
</View>
)
}
export default function App() {
return (
<SafeAreaProvider>
<ThemeProvider>
<PortalHost>
<Screen />
</PortalHost>
</ThemeProvider>
</SafeAreaProvider>
)
}
The Portal component itself renders nothing inline — its children appear inside the host's overlay layer instead. Mount and unmount as you would any normal React component.
How it works
PortalHost wraps its children in a root View and adds an absolutely-positioned overlay layer on top. Each <Portal> registers its children with the host via context; the host renders them in mount order, so the most recently mounted Portal appears on top.
The overlay layer uses pointerEvents="box-none", so empty regions of the overlay pass touches through to the content below. Each Portal entry is responsible for its own positioning and scrim — typically with position: 'absolute' and a full-bleed touch-capturing View.
Stacking order
Portals render in mount order. If you open Dialog A then Dialog B, Dialog B sits above Dialog A. Closing B brings A back to the top.
Without a host
If <Portal> is rendered outside a <PortalHost>, it logs a development error and falls back to inline rendering so your screen still works. Add a PortalHost at the app root to silence the warning and get the overlay behavior.
Portal Props
This component has no custom props.
PortalHost Props
| Prop | Type | Default | Required | Description |
|---|---|---|---|---|
style | StyleProp<ViewStyle> | - | No | Style applied to the root container that wraps host children and the overlay layer. |