Keep content clear of the notch, status bar, and home indicator with One.UI.SafeArea
The safe area is the part of the screen that system UI does not cover: below
the status bar and notch or Dynamic Island, above the home indicator or
Android navigation bar, and clear of display cutouts. One.UI.SafeArea.View
pads its content into that area, and useSafeAreaInsets gives you the raw
numbers for custom layouts.
One mounts the safe area provider at the root of every app, so both work with
no setup. Libraries that import react-native-safe-area-context, such as
React Navigation, resolve to the same implementation and read the same
values. For the component reference in the router docs, see
SafeAreaView.
import { One, Slot } from 'one'
export default function Layout() { return ( <One.UI.SafeArea.View style={{ flex: 1 }} edges={['top']}> <Slot /> </One.UI.SafeArea.View> )}edges={['top']} pads only the top, the usual choice above a tab bar that
already handles the bottom.
The provider is a native view, OneNativeSafeAreaProvider, that measures
itself and sends its insets and frame to JavaScript whenever they change.
safeAreaInsets after every layout pass and when
UIKit reports a safe area change, so rotation, split view, and the
keyboard appearing all trigger a fresh reading.WindowInsets.Type, API 30 and up;
older versions use the system window insets), converted to dp.env(safe-area-inset-*) and reads its computed padding.The provider reports only the part of the window insets that overlaps it. A provider that starts below the status bar reports a top inset of 0, so nested layouts never pad twice.
The first frame is already right. Before any view mounts, One reads the
window insets synchronously from a native module (initialWindowMetrics) and
seeds the root provider with them, so content does not jump when the first
measurement arrives.
The root provider also feeds Tamagui’s safe area state, so Tamagui’s safe
style values and $safe-area-* variables resolve on iOS and Android and
follow rotation.
| One.UI.SafeArea.View | Type | Description |
|---|---|---|
edgesEdgesWhich edges to inset: an array of edges, or a record of edge to mode. Default: all four, additive. | Edges | Which edges to inset: an array of edges, or a record of edge to mode. Default: all four, additive. |
mode"padding" | "margin" = "padding"Apply the insets as padding or as margin. | "padding" | "margin" = "padding" | Apply the insets as padding or as margin. |
childrenReact.ReactNodeThe content. | React.ReactNode | The content. |
The view also takes every View prop. Edges is
('top' | 'right' | 'bottom' | 'left')[], or an object such as
{ top: 'maximum', bottom: 'additive' } where each edge is one of:
| Mode | Result |
|---|---|
| additive | Inset plus the style’s own padding or margin |
| maximum | The larger of the inset and the style’s value |
| off | The style’s value, unchanged |
An edge missing from the array or the record is off.
| Hooks | Type | Description |
|---|---|---|
useSafeAreaInsets() => EdgeInsetsThe current { top, right, bottom, left } insets of the nearest provider, in points (dp on Android). | () => EdgeInsets | The current { top, right, bottom, left } insets of the nearest provider, in points (dp on Android). |
useSafeAreaFrame() => RectThe nearest provider frame { x, y, width, height }. | () => Rect | The nearest provider frame { x, y, width, height }. |
One.UI.SafeArea.Provider creates a nested provider, and
One.UI.SafeArea.initialMetrics holds the synchronous first reading
({ insets, frame }, or null before the native side can measure).
| iOS | Android | Web | |
|---|---|---|---|
| Source | UIView safeAreaInsets | Root window insets | CSS env() insets |
| Covers | Status bar, notch, home indicator | Status, navigation and caption bars, cutouts | What the browser reports |
| Relative to | The provider view | The provider view | The window |
useSafeAreaInsets
throws. SafeAreaView falls back to the initial metrics instead.viewport-fit=cover. Mobile browsers report nonzero
env(safe-area-inset-*) only when the page’s viewport meta tag includes
it.'5%' counts as 0 and is replaced, even on an edge that is off.import { View } from 'react-native'import { useSafeAreaInsets } from 'one'
function Header({ children }: { children: React.ReactNode }) { const insets = useSafeAreaInsets() return <View style={{ paddingTop: insets.top + 8, paddingHorizontal: 16 }}>{children}</View>}Adding your own spacing to the inset keeps the gap the same on devices with and without a notch.
import { One } from 'one'
function Footer({ children }: { children: React.ReactNode }) { return ( <One.UI.SafeArea.View edges={{ bottom: 'maximum' }} style={{ paddingBottom: 16 }}> {children} </One.UI.SafeArea.View> )}With maximum, a phone with a home indicator gets the indicator’s inset, and
a phone without one still gets 16.
Edit this page on GitHub.