maui-safe-area

Safe Area & Edge-to-Edge Layout in .NET MAUI (.NET 10+)

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "maui-safe-area" with this command: npx skills add davidortinau/maui-skills/davidortinau-maui-skills-maui-safe-area

Safe Area & Edge-to-Edge Layout in .NET MAUI (.NET 10+)

Overview

In .NET 10, MAUI introduces the SafeAreaEdges property for precise per-edge control over how content interacts with system bars, notches, display cutouts, and on-screen keyboards. This replaces the legacy iOS-only Page.UseSafeArea

and Layout.IgnoreSafeArea properties with a unified cross-platform API.

Breaking change: In .NET 10, ContentPage defaults to None (edge-to-edge) on all platforms. In .NET 9, Android ContentPage behaved like Container .

Core APIs

SafeAreaRegions enum (flags)

[Flags] public enum SafeAreaRegions { None = 0, // Edge-to-edge — no safe area padding SoftInput = 1 << 0, // Pad to avoid keyboard Container = 1 << 1, // Stay out of bars/notch, flow under keyboard Default = -1, // Platform default for the control type All = 1 << 15 // Obey all safe area insets (most restrictive) }

SoftInput and Container are flags and can be combined: SafeAreaRegions.Container | SafeAreaRegions.SoftInput = respect bars AND keyboard.

SafeAreaEdges struct

SafeAreaEdges is a struct with per-edge SafeAreaRegions values:

public readonly struct SafeAreaEdges { public SafeAreaRegions Left { get; } public SafeAreaRegions Top { get; } public SafeAreaRegions Right { get; } public SafeAreaRegions Bottom { get; }

// Uniform — same value for all edges
public SafeAreaEdges(SafeAreaRegions uniformValue)

// Horizontal/Vertical
public SafeAreaEdges(SafeAreaRegions horizontal, SafeAreaRegions vertical)

// Per-edge
public SafeAreaEdges(SafeAreaRegions left, SafeAreaRegions top,
                     SafeAreaRegions right, SafeAreaRegions bottom)

}

Static presets: SafeAreaEdges.None , SafeAreaEdges.All , SafeAreaEdges.Default

XAML type converter

The XAML type converter follows Thickness-like syntax with comma-separated values:

<!-- Uniform: all edges = Container --> SafeAreaEdges="Container"

<!-- Horizontal, Vertical --> SafeAreaEdges="Container, SoftInput"

<!-- Left, Top, Right, Bottom --> SafeAreaEdges="Container, Container, Container, SoftInput"

Controls that support SafeAreaEdges

Control Default value Notes

ContentPage

None

Edge-to-edge. Breaking change from .NET 9 Android.

Layout (Grid, StackLayout, etc.) Container

Respects bars/notch, flows under keyboard

ScrollView

Default

iOS: maps to UIScrollViewContentInsetAdjustmentBehavior.Automatic . Only Container and None have effect.

ContentView

None

Inherits parent behavior

Border

None

Inherits parent behavior

Usage Patterns

  1. Edge-to-edge content (background images, immersive UIs)

<ContentPage SafeAreaEdges="None"> <Grid SafeAreaEdges="None"> <Image Source="background.jpg" Aspect="AspectFill" /> <VerticalStackLayout Padding="20" VerticalOptions="End"> <Label Text="Overlay text" TextColor="White" FontSize="24" /> </VerticalStackLayout> </Grid> </ContentPage>

Important: The Grid must be explicitly set to SafeAreaEdges="None" because layouts default to Container . Without this, the Grid respects system bars and prevents true edge-to-edge.

  1. Respect all safe areas (forms, critical content)

<ContentPage SafeAreaEdges="All"> <VerticalStackLayout Padding="20"> <Label Text="Safe content" FontSize="18" /> <Entry Placeholder="Enter text" /> <Button Text="Submit" /> </VerticalStackLayout> </ContentPage>

Content is never obscured by system bars, notches, or the keyboard.

  1. Keyboard-aware chat/messaging layout

<ContentPage> <Grid RowDefinitions="*,Auto" SafeAreaEdges="Container, Container, Container, SoftInput"> <ScrollView Grid.Row="0"> <VerticalStackLayout Padding="20" Spacing="10"> <Label Text="Messages" FontSize="24" /> <!-- message items --> </VerticalStackLayout> </ScrollView>

    &#x3C;Border Grid.Row="1"
            BackgroundColor="LightGray"
            Padding="20">
        &#x3C;HorizontalStackLayout Spacing="10">
            &#x3C;Entry Placeholder="Type a message..."
                   HorizontalOptions="Fill" />
            &#x3C;Button Text="Send" />
        &#x3C;/HorizontalStackLayout>
    &#x3C;/Border>
&#x3C;/Grid>

</ContentPage>

Top and sides use Container (avoid bars/notch); bottom uses SoftInput

(avoid keyboard). The Grid handles safe area; ScrollView handles scrolling.

  1. Mixed layout — edge-to-edge header, safe body

<ContentPage SafeAreaEdges="None"> <Grid RowDefinitions="Auto,*,Auto"> <!-- Header: edge-to-edge behind status bar --> <Grid BackgroundColor="Primary"> <Label Text="App Header" TextColor="White" Margin="20,40,20,20" /> </Grid>

    &#x3C;!-- Body: respect safe areas -->
    &#x3C;ScrollView Grid.Row="1" SafeAreaEdges="All">
        &#x3C;VerticalStackLayout Padding="20">
            &#x3C;Label Text="Main content" />
        &#x3C;/VerticalStackLayout>
    &#x3C;/ScrollView>

    &#x3C;!-- Footer: keyboard-aware -->
    &#x3C;Grid Grid.Row="2"
          SafeAreaEdges="SoftInput"
          BackgroundColor="LightGray"
          Padding="20">
        &#x3C;Entry Placeholder="Type a message..." />
    &#x3C;/Grid>
&#x3C;/Grid>

</ContentPage>

  1. Programmatic (C#)

var page = new ContentPage { SafeAreaEdges = SafeAreaEdges.All };

var grid = new Grid { // Per-edge: Container on top/left/right, SoftInput on bottom SafeAreaEdges = new SafeAreaEdges( left: SafeAreaRegions.Container, top: SafeAreaRegions.Container, right: SafeAreaRegions.Container, bottom: SafeAreaRegions.SoftInput) };

Blazor Hybrid Apps

Blazor Hybrid apps use BlazorWebView inside a MAUI ContentPage . Safe area handling is split between XAML (the page) and CSS (the web content).

Recommended approach

  • Set the page to edge-to-edge (default in .NET 10):

<ContentPage SafeAreaEdges="None"> <BlazorWebView HostPage="wwwroot/index.html"> <BlazorWebView.RootComponents> <RootComponent Selector="#app" ComponentType="{x:Type local:Routes}" /> </BlazorWebView.RootComponents> </BlazorWebView> </ContentPage>

  • Add viewport-fit=cover in index.html to let CSS access safe area insets:

<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover" />

  • Use CSS env() functions for safe area insets:

/* Status bar spacer for iOS */ .status-bar-safe-area { display: none; }

@supports (-webkit-touch-callout: none) { .status-bar-safe-area { display: flex; position: sticky; top: 0; height: env(safe-area-inset-top); background-color: var(--header-bg, #f7f7f7); width: 100%; z-index: 1; } }

/* General safe area padding */ body { padding-top: env(safe-area-inset-top); padding-bottom: env(safe-area-inset-bottom); padding-left: env(safe-area-inset-left); padding-right: env(safe-area-inset-right); }

Available CSS environment variables:

  • env(safe-area-inset-top) — status bar, notch, Dynamic Island

  • env(safe-area-inset-bottom) — home indicator, navigation bar

  • env(safe-area-inset-left) — landscape left edge

  • env(safe-area-inset-right) — landscape right edge

Common Blazor Hybrid gotcha

Do NOT combine XAML safe area padding with CSS safe area padding. If you set SafeAreaEdges="Container" on the ContentPage AND use env(safe-area-inset-*)

in CSS, you get double padding. Choose one approach:

  • XAML approach: Set SafeAreaEdges="Container" or "All" on the page/layout and do NOT use env() in CSS.

  • CSS approach (recommended for Blazor): Leave the page at SafeAreaEdges="None"

(default) and handle all insets in CSS with env() . This gives finer control for web-based layouts.

Platform-Specific Behavior

iOS & Mac Catalyst

  • Safe area insets include: status bar, navigation bar, tab bar, notch/Dynamic Island, home indicator

  • SoftInput includes the keyboard when visible

  • Insets update automatically on rotation and UI visibility changes

  • ScrollView with Default maps to UIScrollViewContentInsetAdjustmentBehavior.Automatic

Reading safe area insets at runtime (iOS only):

using Microsoft.Maui.Controls.PlatformConfiguration; using Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific;

Thickness insets = On<iOS>().SafeAreaInsets(); // insets.Top, insets.Bottom, insets.Left, insets.Right

Transparent navigation bar for edge-to-edge under nav bar:

<!-- Shell --> <Shell Shell.BackgroundColor="#80000000" Shell.NavBarHasShadow="False" />

<!-- NavigationPage --> <NavigationPage BarBackgroundColor="#80000000" ios:NavigationPage.HideNavigationBarSeparator="True" />

Android

  • Safe area insets include: system bars (status/navigation) and display cutouts

  • SoftInput includes the soft keyboard

  • Behavior varies by Android version and edge-to-edge settings

  • MAUI uses WindowInsetsCompat and WindowInsetsAnimationCompat internally

Breaking change (.NET 9 → 10): ContentPage default changed from Container → None . Set SafeAreaEdges="Container" explicitly to restore .NET 9 behavior.

WindowSoftInputModeAdjust interaction: If you were using WindowSoftInputModeAdjust.Resize in .NET 9, you may need SafeAreaEdges="All" on the ContentPage to maintain keyboard avoidance.

Migration from Legacy APIs

From ios:Page.UseSafeArea (iOS-only)

<!-- .NET 9 (legacy) --> <ContentPage xmlns:ios="clr-namespace:Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific;assembly=Microsoft.Maui.Controls" ios:Page.UseSafeArea="True">

<!-- .NET 10+ (cross-platform) --> <ContentPage SafeAreaEdges="Container">

From Layout.IgnoreSafeArea

<!-- .NET 9 (legacy) --> <Grid IgnoreSafeArea="True">

<!-- .NET 10+ --> <Grid SafeAreaEdges="None">

The legacy properties still work but are obsolete. IgnoreSafeArea="True" maps internally to SafeAreaRegions.None .

Common Gotchas

Layouts default to Container , not None . For true edge-to-edge, you must set SafeAreaEdges="None" on BOTH the page AND any layout inside it.

SoftInput on ScrollView has no effect. ScrollView manages its own content insets. For keyboard avoidance with ScrollView, wrap it in a Grid or StackLayout and set SafeAreaEdges on the wrapper.

Default is not None . Default means "use platform defaults for this control type." None means "edge-to-edge, no padding." They differ on ScrollView especially.

Blazor double-padding. Don't combine XAML SafeAreaEdges="Container" with CSS env(safe-area-inset-*) . Use one or the other.

Android .NET 9 → 10 regression. If your Android app's content suddenly goes under the status bar after upgrading, add SafeAreaEdges="Container" to your ContentPage.

Shell/NavigationPage on iOS. Content won't extend behind the nav bar unless you set a transparent background color AND hide the separator line.

Best Practices

Choose the right value:

  • All — forms, critical inputs that must always be visible

  • None — photo viewers, video players, games, background images

  • Container — scrollable content with fixed headers/footers

  • SoftInput — messaging/chat UIs with bottom input bars

Test on multiple devices:

  • Devices with notches (iPhone X+, Android cutouts)

  • Tablets in landscape

  • Different aspect ratios and screen sizes

Combine SafeAreaEdges with Padding: SafeAreaEdges controls automatic safe area insets. Add your own Padding for visual spacing on top:

<ContentPage SafeAreaEdges="All"> <VerticalStackLayout Padding="20"> <!-- Both safe area and visual padding applied --> </VerticalStackLayout> </ContentPage>

Use per-control settings to create layouts where different sections have different safe area behavior (edge-to-edge header + safe body + keyboard-aware footer).

For Blazor Hybrid apps, prefer the CSS env() approach and leave the ContentPage at SafeAreaEdges="None" .

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

maui-performance

No summary provided by upstream source.

Repository SourceNeeds Review
General

maui-data-binding

No summary provided by upstream source.

Repository SourceNeeds Review
General

maui-rest-api

No summary provided by upstream source.

Repository SourceNeeds Review
General

maui-shell-navigation

No summary provided by upstream source.

Repository SourceNeeds Review