React Native – Safe Areas, Notches and the Keyboard

June 25, 20263 min readUpdated 8/24/2026

A phone screen is not a rectangle you own. A notch, a camera cut-out, the home indicator and rounded corners all eat into it, and content placed at the top of the window is drawn underneath them.

There is no web equivalent, and no amount of flexbox solves it — the amounts differ per device and per orientation, so they cannot be constants.

useSafeAreaInsets

The react-native-safe-area-context library reports how much to keep clear on each edge. It is a hook, so the values update when the device rotates:

  const insets = useSafeAreaInsets();
  const paddingBottom = insets.bottom + theme.spacing.lg + bottomInset;

Four numbers — top, bottom, left, right. On an iPhone with a home indicator, bottom is about 34; on an older device it is 0.

The provider has to wrap the app for the hook to work. It is the outermost provider in the demo app for exactly that reason — the screen shell and the toast host both read insets, so it has to sit above both.

Applying it once, not per screen

Every screen needs this, so no screen should implement it. Wrap it:

  return (
    <ScrollView
      testID={testID}
      style={styles.screen}
      contentContainerStyle={[padded && styles.padded, { paddingBottom }, contentStyle]}
      keyboardShouldPersistTaps="handled"
      keyboardDismissMode="on-drag"
    >
      {children}
    </ScrollView>
  );

Only the bottom inset is applied by default here, which is worth explaining. The navigation header already handles the top — it draws itself below the notch — so adding insets.top as well would leave a visible gap. The bottom is the one that matters: a "Pay" button flush against the bottom edge sits under the home indicator and is genuinely hard to press.

style versus contentContainerStyle

A ScrollView has two, and confusing them is a rite of passage. style is the scroll view itself — the window you look through, where flex: 1 belongs. contentContainerStyle is the content that moves inside it, and it is where padding belongs. Put padding on style and it pads the window, clipping the content instead of insetting it.

SafeAreaView, and why it is not enough

There is a SafeAreaView component that applies insets as padding automatically. It is fine for simple cases and has two real limits: it is iOS-only in React Native's own implementation, and it applies all four edges, which is rarely what you want. Reading the insets and deciding yourself is more code and far more predictable.

The keyboard

The second thing that takes your screen away. When it opens, roughly 40% of the display is gone and whatever the user was typing into may now be behind it.

      <KeyboardAvoidingView
        style={styles.fill}
        behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
      >

⚠️ The platform split is required, not defensive. Android resizes the app window itself when the keyboard appears, so 'height' cooperates with that. iOS does not, so it needs 'padding'. Using one value for both gives you a screen that works correctly on one platform and looks broken on the other — and if you only ever test on a simulator, you will not see which.

Dismissing it

There is no click-outside-to-blur. Two props on the scroll view handle it:

      keyboardShouldPersistTaps="handled"
      keyboardDismissMode="on-drag"

keyboardDismissMode="on-drag" closes the keyboard when the user scrolls, which is what they expect.

keyboardShouldPersistTaps is the subtle one and it fixes a real complaint. The default is 'never': while the keyboard is open, the first tap anywhere is consumed to dismiss it — so tapping a button does nothing and the user has to tap twice. 'handled' means a tap that something else handles goes through, while a tap on empty background still dismisses. You want both, and only this value gives you both.

Where the pieces sit

Worth stating plainly, because the layering trips people up. The safe-area provider wraps everything. The navigation container handles the top inset via its header. Each screen applies the bottom inset. A modal or a bottom sheet is its own window, so it reads the insets again itself — being inside a screen that already applied them does not help it.

What is next

Lists with FlatList — and why ScrollView is the wrong answer past about a dozen rows.