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.

.NET 10 MAUI Android 16 Edge-to-Edge Fix

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:

  1. Embrace it. Call EnableEdgeToEdge() (from AndroidX Activity 1.10+) and consume insets yourself.
  2. 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;
        });
    }
}

Then wire it up in MauiProgram:

builder.ConfigureLifecycleEvents(events =>
{
#if ANDROID
    events.AddAndroid(android => android
        .OnCreate((activity, _) =>
        {
            AndroidX.Activity.EdgeToEdge.EnableEdgeToEdge(activity);
            InsetsTracker.Attach(activity);
        }));
#endif
});

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:

<style name="LegacyTheme" parent="Maui.SplashTheme">
  <item name="android:windowOptOutEdgeToEdgeEnforcement">true</item>
</style>

Then route that page to a sub-activity using that theme. Android 17 removes the opt-out, so treat this as a stopgap.

Things that did not work

  • Window.SetStatusBarColor — silently ignored on API 36.
  • CommunityToolkit.Maui StatusBarBehavior — colors apply but layout is still broken without insets.
  • Setting Page.Padding from OnAppearing — fires before insets are dispatched on cold start; first frame still clips.

Closing

The full write-up on mobiletechlead.com has the manifest diff, a screenshot before/after, and the benchmark numbers showing the inset listener costs roughly 0.1 ms per layout pass on a Pixel 8: https://mobiletechlead.com/article/dotnet-10-maui-android-16-edge-to-edge-fix

If you found a cleaner pattern — particularly one that avoids the static InsetsTracker — I would love to hear it.

Article changelog (1)
  • — Expanded with TL;DR, table of contents, or additional sections
Marcus Chen
About the Author Marcus Chen

Senior mobile architect with a decade of cross-platform experience. Spent the last five years going deep on .NET MAUI in production.