react-native-safe-area-context
react-native-safe-area-context measures the usable screen frame and system insets around notches, rounded corners, status bars, home indicators, and similar obstructions. A SafeAreaProvider establishes the coordinate frame, while SafeAreaView applies native padding or margins and hooks expose numeric top, right, bottom, and left values for custom layouts. Version 5 supports React Native 0.74 and newer across iOS, Android, web, macOS, and Windows, with initial metrics for faster first paint and SSR plus testing helpers for deterministic layouts.
This is the default safe-area choice for current React Native applications and is often already present through navigation or Expo tooling. Install it for system insets, but do not confuse safe areas with keyboard avoidance or apply padding twice around navigator-managed UI.
Use it if
- Your React Native screen draws edge to edge and must avoid notches, status bars, or home indicators
- You use React Navigation, Expo, modals, or native screens that already expect this package's provider and inset hooks
- You need safe-area behavior on Android and web as well as iOS
- You need native SafeAreaView performance plus direct inset numbers for floating controls or custom animation
- You are on React Native below 0.74: the 5.x documentation requires 0.74 or newer, so use a compatible 4.x release or upgrade React Native
- Your real obstruction is the software keyboard: safe-area insets deliberately exclude it, and the project recommends a keyboard-specific library instead
- A navigation header or tab bar already consumes the relevant inset: adding another all-edge SafeAreaView can create obvious double padding
- You require settled New Architecture compatibility across older React Native versions: the docs still call that support experimental and warn that only the latest React Native will be supported
Setup reality
The JavaScript package has no runtime dependencies and includes types, but it contains native code. Bare React Native iOS apps may need CocoaPods installation and a rebuild; Expo projects should use Expo's version-aware installer. Put one provider near the app root and extra providers at native modal or route roots when their coordinate frame changes. Do not place providers inside animated views or scroll views, and configure initial metrics, web SSR, and Jest transforms deliberately.
Patterns
Add the provider at the app rootprovide-safe-area
import { SafeAreaProvider } from 'react-native-safe-area-context'
export default function App() {
return (
<SafeAreaProvider>
<RootNavigator />
</SafeAreaProvider>
)
}Consumers measure insets relative to the nearest provider. Keep the root provider out of ScrollView and Animated containers.
Avoid a delayed first inset updateseed-initial-metrics
import {
SafeAreaProvider,
initialWindowMetrics,
} from 'react-native-safe-area-context'
<SafeAreaProvider initialMetrics={initialWindowMetrics}>
<RootNavigator />
</SafeAreaProvider>Do not use initialWindowMetrics if this provider can remount or when using react-native-navigation; the cached value can be stale or null.
Apply native safe-area paddingpad-screen-edges
import { SafeAreaView } from 'react-native-safe-area-context'
function Screen({ children }) {
return (
<SafeAreaView style={{ flex: 1 }} edges={['top', 'right', 'bottom', 'left']}>
{children}
</SafeAreaView>
)
}SafeAreaView applies insets natively and is the docs' preferred consumer when fixed padding or margin is enough.
Avoid padding an edge already handledskip-navigator-edge
<SafeAreaView
style={{ flex: 1 }}
edges={['right', 'bottom', 'left']}
>
<ScreenContent />
</SafeAreaView>Omitting top is useful when a navigation header already handles it; otherwise the screen may show double top spacing.
Keep a minimum bottom gutterset-minimum-bottom-space
<SafeAreaView
style={{ paddingBottom: 24 }}
edges={{ bottom: 'maximum' }}
>
<FloatingControls />
</SafeAreaView>maximum uses max(safe inset, style spacing). The default additive mode would add 24 to the inset instead.
Apply an inset as marginapply-safe-margin
<SafeAreaView
mode="margin"
edges={['left', 'right']}
style={{ height: 1, backgroundColor: '#ddd' }}
/>mode defaults to padding. Margin mode is useful for separators or backgrounds that should not extend into unsafe edges.
Use inset numbers in a custom layoutread-insets-hook
import { useSafeAreaInsets } from 'react-native-safe-area-context'
function BottomButton() {
const insets = useSafeAreaInsets()
return (
<Pressable style={{ marginBottom: Math.max(insets.bottom, 16) }}>
<Text>Continue</Text>
</Pressable>
)
}Hook values update through JavaScript and can lag during rotation, so prefer SafeAreaView when direct padding is sufficient.
Observe frame and inset changeslisten-without-rerender
import { SafeAreaListener } from 'react-native-safe-area-context'
<SafeAreaListener
onChange={({ insets, frame }) => {
analytics.recordLayout({ insets, frame })
}}
>
<Content />
</SafeAreaListener>SafeAreaListener calls onChange without making a hook consumer rerender; do not send high-frequency layout data blindly to analytics.
Create a provider for native modal contentprovide-modal-frame
<Modal visible={open} onRequestClose={onClose}>
<SafeAreaProvider>
<SafeAreaView style={{ flex: 1 }}>
<ModalContent />
</SafeAreaView>
</SafeAreaProvider>
</Modal>Native modals and some react-native-screens routes may need their own provider because their coordinate frame differs from the app root.
Use the built-in Jest mockmock-in-jest
// jest.setup.js
import mockSafeAreaContext from 'react-native-safe-area-context/jest/mock'
jest.mock('react-native-safe-area-context', () => mockSafeAreaContext)The built-in mock uses zero insets. Add the package to Jest transformIgnorePatterns if Babel reports an import statement outside a module.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| react-native-keyboard-controller | npm | Your layout problem is keyboard overlap, which safe-area insets intentionally do not include |
| react-native-static-safe-area-insets | npm | A legacy iOS-only integration needs static inset constants and does not need this package's live cross-platform context |