maui-safe-area
Safe Area — Gotchas & Best Practices (.NET 10+)
Breaking Changes
⚠️ .NET 9 → 10: ContentPage default changed to None
ContentPage now defaults to edge-to-edge (None) on all platforms. In .NET 9,
Android ContentPage behaved like Container. If your Android content goes under
the status bar after upgrading, add SafeAreaEdges="Container" explicitly.
<!-- ❌ .NET 10 default — content goes under status bar on Android -->
<ContentPage>
<!-- ✅ Restore .NET 9 Android behavior -->
<ContentPage SafeAreaEdges="Container">
⚠️ WindowSoftInputModeAdjust.Resize migration
If you used WindowSoftInputModeAdjust.Resize in .NET 9, you may need
SafeAreaEdges="All" on the ContentPage to maintain keyboard avoidance.
Common Gotchas
1. Layouts default to Container, not None
For true edge-to-edge, set SafeAreaEdges="None" on both the page and layouts:
<!-- ❌ Grid still respects system bars (its default is Container) -->
<ContentPage SafeAreaEdges="None">
<Grid>
<Image Source="bg.jpg" Aspect="AspectFill" />
</Grid>
</ContentPage>
<!-- ✅ Both page and layout set to None -->
<ContentPage SafeAreaEdges="None">
<Grid SafeAreaEdges="None">
<Image Source="bg.jpg" Aspect="AspectFill" />
</Grid>
</ContentPage>
2. SoftInput on ScrollView has no effect
ScrollView manages its own content insets. Wrap it in a Grid/StackLayout instead:
<!-- ❌ SoftInput is ignored on ScrollView -->
<ScrollView SafeAreaEdges="SoftInput">
<!-- ✅ Set SoftInput on the wrapper layout -->
<Grid SafeAreaEdges="Container, Container, Container, SoftInput">
<ScrollView> ... </ScrollView>
</Grid>
3. Default is not None
Default = "use platform defaults for this control type." None = "edge-to-edge."
They differ significantly on ScrollView (iOS maps Default to automatic content insets).
4. Blazor Hybrid double-padding
<!-- ❌ XAML safe area + CSS env() = double padding -->
<ContentPage SafeAreaEdges="Container">
<BlazorWebView ... /> <!-- CSS also uses env(safe-area-inset-*) -->
<!-- ✅ Pick ONE approach — CSS is recommended for Blazor -->
<ContentPage SafeAreaEdges="None">
<BlazorWebView ... /> <!-- CSS handles all insets with env() -->
5. iOS Shell/NavigationPage edge-to-edge
Content won't extend behind the nav bar unless you set a transparent background AND hide the separator:
<Shell Shell.BackgroundColor="#80000000"
Shell.NavBarHasShadow="False" />
Decision Framework
| Scenario | SafeAreaEdges value |
|---|---|
| Forms, critical inputs | All |
| Photo viewer, video player, game | None (on page AND layout) |
| Scrollable content with fixed header/footer | Container |
| Chat/messaging with bottom input bar | Per-edge: Container, Container, Container, SoftInput |
| Blazor Hybrid app | None on page, CSS env() for insets |
Migration Quick Reference
| Legacy (.NET 9) | New (.NET 10+) |
|---|---|
ios:Page.UseSafeArea="True" |
SafeAreaEdges="Container" |
IgnoreSafeArea="True" |
SafeAreaEdges="None" |
WindowSoftInputModeAdjust.Resize |
SafeAreaEdges="All" on ContentPage |
Legacy properties still work but are obsolete.
Best Practices
-
Combine
SafeAreaEdgeswithPadding— safe area handles insets,Paddingadds visual spacing:<ContentPage SafeAreaEdges="All"> <VerticalStackLayout Padding="20"> <!-- Both applied — no conflict --> </VerticalStackLayout> </ContentPage> -
Use per-control settings for mixed layouts (edge-to-edge header + safe body + keyboard-aware footer).
-
For Blazor Hybrid, prefer CSS
env()and leaveSafeAreaEdges="None". Addviewport-fit=coverto the<meta viewport>tag. -
Test on notched devices (iPhone X+, Android cutouts), tablets in landscape, and varying screen sizes.
Checklist
- Android upgrade:
SafeAreaEdges="Container"added if content goes under status bar - Edge-to-edge:
Noneset on both page and layout - ScrollView keyboard avoidance uses wrapper Grid, not ScrollView's own
SafeAreaEdges - Blazor Hybrid: using either XAML or CSS safe areas, not both
-
viewport-fit=coverin Blazor'sindex.html<meta viewport>tag - Legacy
UseSafeArea/IgnoreSafeAreamigrated toSafeAreaEdges