Fixing the .NET 10 MAUI status bar overlap on Android 16 (edge-to-edge)
If you upgraded a .NET MAUI app to .NET 10 and then ran it on a device or emulator with Android 16 (API 36), you may have noticed something ugly: the status bar now sits on top of your Shell header.
If you upgraded a .NET MAUI app to .NET 10 and then ran it on a device or emulator with Android 16 (API 36), you may have noticed something ugly: the status bar now sits on top of your Shell header, and the bottom tab bar disappears behind the gesture navigation pill.
This is not a bug in your app. It is Android 16 finally enforcing edge-to-edge for everyone whose targetSdk is 36, and .NET MAUI 10 does not yet ship a default handler that pushes window insets into the visual tree.
Here is what is happening and how I worked around it without rewriting the whole layout.
What changed in Android 16
Starting with API 36, calling Window.SetDecorFitsSystemWindows(true) is a no-op, and Window.SetStatusBarColor is deprecated. The system draws your app full-bleed under the status bar and the gesture nav, and it is your job to pad your content using WindowInsets.
Google gives you two doors:
Embrace it. Call EnableEdgeToEdge() (from AndroidX Activity 1.10+) and consume insets yourself.
Opt out per-screen with android:windowOptOutEdgeToEdgeEnforcement="true" in your activity theme (Android 16 only, removed in 17).
MAUI Shell does neither out of the box.
Minimum repro
dotnet new maui -n EdgeRepro
cd EdgeRepro
dotnet build -t:Run -f net10.0-android
Run on a Pixel emulator running Android 16. The Shell title bar is clipped. Set the page background to red and you can see content drawing all the way under the status bar.
The fix
The cleanest fix I found is a small MauiAppBuilder lifecycle hook that listens for window insets on the root decor view and publishes the top/bottom values as a static property pages can bind to.
// Platforms/Android/InsetsTracker.cs
using Android.Views;
using AndroidX.Core.View;
public static class InsetsTracker
{
public static Thickness Safe { get; private set; } = new(0);
public static event Action? Changed;
public static void Attach(Android.App.Activity activity)
{
var root = activity.Window!.DecorView;
ViewCompat.SetOnApplyWindowInsetsListener(root, (v, insets) =>
{
var bars = insets.GetInsets(
WindowInsetsCompat.Type.SystemBars()
| WindowInsetsCompat.Type.DisplayCutout());
var density = activity.Resources!.DisplayMetrics!.Density;
Safe = new Thickness(
bars.Left / density,
bars.Top / density,
bars.Right / density,
bars.Bottom / density);
Changed?.Invoke();
return insets;
});
}
}
Now in your AppShell.xaml.cs (or any base page), subscribe and push the inset to a bindable property:
public partial class AppShell : Shell
{
public AppShell()
{
InitializeComponent();
#if ANDROID
Apply();
InsetsTracker.Changed += Apply;
#endif
}
void Apply()
{
Padding = new Thickness(
0, InsetsTracker.Safe.Top,
0, InsetsTracker.Safe.Bottom);
}
}
That is enough to get the Shell chrome back under the status bar and above the gesture nav.
Per-page opt-out (legacy screens)
If you have one screen — a video player, a camera view — that genuinely needs the old behavior, do not opt the whole app out. Apply a per-activity theme override:
Build custom haptic feedback in .NET MAUI 10 by calling iOS CoreHaptics and Android VibrationEffect directly. Full platform code, DI wiring, and the API pitfalls that break on iPad, low-power Android, and the iOS Simulator.
Ship iOS Quick Actions and Android app shortcuts from one .NET MAUI 10 codebase, with static, dynamic, and pinned shortcuts, routing, and localization.
A working developer guide to CommunityToolkit MediaElement in .NET MAUI 10: install, XAML setup, HLS/DASH streaming, background audio, PiP, and fixes for the errors you will actually hit.