All articles
Mobile DevelopmentJune 3, 2026 6 min read

Handling Android 15's Edge-to-Edge Mandate in React Native and Expo

Android 15 forces edge-to-edge by default and it's quietly breaking React Native UIs. Here's what changed, how to fix it in Expo, and where react-native-safe-area-context still gets it wrong.

Handling Android 15's Edge-to-Edge Mandate in React Native and Expo

If you targeted Android 15 (API 35) and suddenly your bottom tab bar is sitting under the gesture pill, or your header is being eaten by the status bar, you're not alone. Google quietly made edge-to-edge the default, and a lot of React Native apps that looked fine on Android 14 now look broken on a Pixel 9. Here's what actually changed and how we've been fixing it on real projects.

What Google actually changed

Starting with apps that compile against targetSdk = 35, Android enforces edge-to-edge rendering by default. That means Window.setDecorFitsSystemWindows(false) is essentially the new baseline — your content draws behind the status bar and the navigation bar, and you are responsible for insetting it.

Before Android 15, you had to opt in. The system would pad your layout to avoid the system bars unless you explicitly drew under them. Now it's flipped: the system assumes you've handled insets, and if you haven't, your UI overlaps the system chrome.

This isn't bad design — it's actually closer to how iOS has always worked with safe areas. The problem is that the React Native ecosystem grew up under the old assumption, and a lot of community libraries and even some Expo modules haven't fully caught up.

Who this hits hardest

  • Apps using react-native-screens with custom headers
  • Anything with a BottomTabNavigator that doesn't use the inset hook
  • Modals and bottom sheets
  • Camera and full-screen video views (these usually want edge-to-edge — but now have to handle the inverse case)
  • Anything that hardcoded paddingTop: 24 for the status bar (you know who you are)

How to tell if you're affected

Run your app on an Android 15 device or emulator with targetSdkVersion set to 35. If you see any of these symptoms, you've got work to do:

  • Status bar icons sitting on top of your header text
  • Bottom navigation overlapping the gesture indicator
  • White or transparent strips where the system bars used to push your content down
  • StatusBar component props (like backgroundColor) silently doing nothing

That last one is important: on Android 15, StatusBar.setBackgroundColor is deprecated. The status bar is always transparent now. If you were relying on it for a colored top bar, you need a different approach.

The fix in Expo SDK 52+

Expo SDK 52 ships an edgeToEdge config plugin that handles the AndroidManifest and theme changes for you. If you're on bare React Native, you'll need to do this manually in your styles.xml.

{
  "expo": {
    "android": {
      "edgeToEdgeEnabled": true
    },
    "plugins": [
      [
        "expo-build-properties",
        {
          "android": {
            "compileSdkVersion": 35,
            "targetSdkVersion": 35
          }
        }
      ]
    ]
  }
}

With edgeToEdgeEnabled: true, Expo applies the react-native-edge-to-edge library under the hood (originally by Mathieu Acthernoene), which gives you a sane default theme and a SystemBars component that actually respects Android 15's rules.

Replacing the StatusBar component

This is the part that trips most teams up. The old pattern:

import { StatusBar } from 'expo-status-bar';

<StatusBar style="dark" backgroundColor="#ffffff" />

The backgroundColor prop is a no-op on Android 15. Worse, depending on your theme, the status bar icons might be invisible against your header.

Use SystemBars from react-native-edge-to-edge instead:

import { SystemBars } from 'react-native-edge-to-edge';

export default function RootLayout() {
  return (
    <>
      <SystemBars style="dark" />
      <YourAppContent />
    </>
  );
}

This controls icon tint only. If you want a colored status bar background, you draw it yourself with a View that has the top inset applied as padding.

Applying insets correctly

Here's where most teams get it wrong. react-native-safe-area-context works fine on Android 15, but you need to actually use it everywhere — not just at the root.

import { SafeAreaProvider, useSafeAreaInsets } from 'react-native-safe-area-context';

function Header() {
  const insets = useSafeAreaInsets();
  return (
    <View
      style={{
        paddingTop: insets.top,
        backgroundColor: '#0e7490',
      }}
    >
      <Text style={{ color: 'white', padding: 16 }}>Dashboard</Text>
    </View>
  );
}

export default function App() {
  return (
    <SafeAreaProvider>
      <Header />
    </SafeAreaProvider>
  );
}

A common mistake we see: wrapping the whole app in <SafeAreaView> with edges={['top', 'bottom']}. That works, but it forces a single background color across the whole screen. If you want a colored header that bleeds into the status bar, you need to apply the inset as paddingTop on the header itself, not pad the parent.

The bottom inset trap

Android 15 reports a non-zero bottom inset on devices with gesture navigation, even though the gesture bar is translucent. Your bottom tab bar needs to add this inset to its height, or buttons get hard to tap.

If you use React Navigation, the bottom tab bar handles this automatically in v7. But custom bottom sheets, FABs, and floating buttons don't. Always pull the bottom inset and add it as padding:

const insets = useSafeAreaInsets();

<Pressable
  style={{
    position: 'absolute',
    bottom: 16 + insets.bottom,
    right: 16,
  }}
>
  <Text>FAB</Text>
</Pressable>

The keyboard problem

This one bit us hard on a fintech app. With edge-to-edge enabled, the default softInputMode behavior changes. adjustResize no longer reliably shrinks your layout when the keyboard appears, because the system assumes you're handling insets dynamically.

The fix is to use KeyboardAvoidingView with the padding behavior on Android (not height), and combined with the IME inset from react-native-safe-area-context 5.x, which exposes useSafeAreaInsets updates when the keyboard is shown.

If you're still on an older version of safe-area-context, upgrade. The pre-5.x versions don't track IME insets, and you'll fight the keyboard forever.

Native modules that haven't caught up

We've hit issues with a few popular libraries on Android 15. Check these specifically:

  • react-native-modal — older versions draw under the status bar with no inset handling. Either upgrade or wrap your modal content in a SafeAreaView.
  • @gorhom/bottom-sheet — works fine in recent versions but you need to pass bottomInset explicitly if you want the sheet to stop above the gesture bar.
  • Camera libraries — most want edge-to-edge anyway, but double-check your overlay UI (capture button, flash toggle) is inset properly.
  • WebView — content that uses CSS env(safe-area-inset-*) may render incorrectly because Android WebView doesn't always plumb these values through. You may need to inject them as CSS variables manually.

When to bother fixing this vs delaying targetSdk

Google Play requires apps to target API 35 by August 31, 2025 for updates to existing apps, and a year earlier for new apps. You can't dodge this forever. But if you have a release coming up next week, here's the pragmatic call:

  • If your app is mostly content (lists, forms, navigation), the fix is usually a day or two of work.
  • If you have lots of custom layout, full-screen views, or older third-party UI components, budget a sprint.
  • Don't ship targetSdk = 35 to production without testing on at least a Pixel 8 or 9 running Android 15 stock. The emulator gets close but misses gesture-nav edge cases.

If you're on Expo, upgrading to SDK 52 or later is the single highest-leverage move. The edgeToEdgeEnabled flag gets you 70% of the way there. The remaining 30% is auditing every screen for hardcoded paddings and missing inset hooks.

Where we'd start

If you're walking into this cold on a Monday morning: bump to Expo SDK 52 (or align bare RN to RN 0.76+), set edgeToEdgeEnabled: true, swap expo-status-bar for SystemBars from react-native-edge-to-edge, and do a screen-by-screen pass on an Android 15 device looking for hardcoded paddingTop or marginBottom values. Replace them with useSafeAreaInsets. Test your modals, keyboards, and any full-screen views last — those are where the subtle bugs hide.

If you'd rather not own this audit yourself, our mobile team does Android 15 readiness reviews on existing React Native and Expo codebases. Either way, don't ship blind — emulators lie about gesture navigation insets in ways that will absolutely embarrass you in a TestFlight equivalent.

#React Native#Expo#Android#Mobile Development

Want a team like ours?

72Technologies builds production software for the kind of teams who actually read this blog.

Start a project