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.
2. 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.
3. 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>
<Border Grid.Row="1"
BackgroundColor="LightGray"
Padding="20">
<HorizontalStackLayout Spacing="10">
<Entry Placeholder="Type a message..."
HorizontalOptions="Fill" />
<Button Text="Send" />
</HorizontalStackLayout>
</Border>
</Grid>
</ContentPage>
Top and sides use Container (avoid bars/notch); bottom uses SoftInput
(avoid keyboard). The Grid handles safe area; ScrollView handles scrolling.
4. 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>
<!-- Body: respect safe areas -->
<ScrollView Grid.Row="1" SafeAreaEdges="All">
<VerticalStackLayout Padding="20">
<Label Text="Main content" />
</VerticalStackLayout>
</ScrollView>
<!-- Footer: keyboard-aware -->
<Grid Grid.Row="2"
SafeAreaEdges="SoftInput"
BackgroundColor="LightGray"
Padding="20">
<Entry Placeholder="Type a message..." />
</Grid>
</Grid>
</ContentPage>
5. 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=coverinindex.htmlto 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 Islandenv(safe-area-inset-bottom)— home indicator, navigation barenv(safe-area-inset-left)— landscape left edgeenv(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 useenv()in CSS. - CSS approach (recommended for Blazor): Leave the page at
SafeAreaEdges="None"(default) and handle all insets in CSS withenv(). 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
SoftInputincludes the keyboard when visible- Insets update automatically on rotation and UI visibility changes
ScrollViewwithDefaultmaps toUIScrollViewContentInsetAdjustmentBehavior.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
SoftInputincludes the soft keyboard- Behavior varies by Android version and edge-to-edge settings
- MAUI uses
WindowInsetsCompatandWindowInsetsAnimationCompatinternally
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, notNone. For true edge-to-edge, you must setSafeAreaEdges="None"on BOTH the page AND any layout inside it. -
SoftInputon ScrollView has no effect. ScrollView manages its own content insets. For keyboard avoidance with ScrollView, wrap it in a Grid or StackLayout and setSafeAreaEdgeson the wrapper. -
Defaultis notNone.Defaultmeans "use platform defaults for this control type."Nonemeans "edge-to-edge, no padding." They differ on ScrollView especially. -
Blazor double-padding. Don't combine XAML
SafeAreaEdges="Container"with CSSenv(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 visibleNone— photo viewers, video players, games, background imagesContainer— scrollable content with fixed headers/footersSoftInput— 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:
SafeAreaEdgescontrols automatic safe area insets. Add your ownPaddingfor 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 atSafeAreaEdges="None".