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.

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-screenswith custom headers - Anything with a
BottomTabNavigatorthat 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: 24for 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
StatusBarcomponent props (likebackgroundColor) 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
bottomInsetexplicitly 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 = 35to 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.
Want a team like ours?
72Technologies builds production software for the kind of teams who actually read this blog.
Start a projectKeep reading

In-App Purchases in React Native: What Apple and Google Actually Reject in 2026
A practical breakdown of the IAP rejection patterns we keep seeing in React Native and Expo apps in 2026 — from receipt validation to subscription restore flows — with code you can lift straight into your project.

App Size Bloat in React Native: How We Got an Expo App Back Under 40 MB
An Expo app crept past 120 MB on Android. Here's the audit we ran, the libraries we ripped out, and the build flags that actually moved the needle.
Deep Linking in React Native 2026: Universal Links, App Links, and the Edge Cases That Bite
Deep linking looks trivial until a marketing campaign goes live and half your users land on the App Store instead of the screen you promised. Here's what actually works in 2026.
