Fix safe area CSS in Blazor Hybrid templates for Android - #34463
Conversation
Remove @supports (-webkit-touch-callout: none) wrapper that prevented safe area CSS rules from applying on Android (Chromium WebView). The env(safe-area-inset-*) values resolve to 0px on platforms that don't need them, so there is no downside to applying them universally. Also update background-color from #f7f7f7 to rgb(3, 23, 62) to match the sidebar theme, and add fallback values to env() functions. Fixes #34462 Co-authored-by: mattleibow <1096616+mattleibow@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Updates the Blazor Hybrid MAUI templates’ safe-area CSS so it applies on Android (Chromium WebView) instead of being gated by a WebKit-only @supports query, addressing content rendering behind the status bar on edge-to-edge Android.
Changes:
- Removes
@supports (-webkit-touch-callout: none)wrapper so safe-area rules are not iOS-only. - Makes
.status-bar-safe-areaalways present withenv(safe-area-inset-top, 0px)and updates its background color. - Adds
env(..., 0px)fallback usage for left inset padding.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| src/Templates/src/templates/maui-blazor/wwwroot/app.css | Makes safe-area spacer and left inset padding apply universally (not WebKit-gated). |
| src/Templates/src/templates/maui-blazor-solution/MauiApp.1/wwwroot/app.css | Mirrors the same safe-area CSS adjustments in the solution template. |
|
/review -b feature/refactor-copilot-yml |
|
/review -b feature/refactor-copilot-yml -p android |
MauiBot
left a comment
There was a problem hiding this comment.
Expert Review — 2 findings
See inline comments for details.
| position: sticky; | ||
| top: 0; | ||
| height: env(safe-area-inset-top, 0px); | ||
| background-color: rgb(3, 23, 62); |
There was a problem hiding this comment.
[major] Template safe-area behavior — This hard-codes the sample app's dark navigation color into the universal host CSS. app.css is included even when SampleContent is disabled, and index.html always emits .status-bar-safe-area, so empty Blazor Hybrid templates can show an unrelated dark strip behind the Android status bar whenever env(safe-area-inset-top) is non-zero. It also risks poor contrast with Android light status-bar icons. Please keep the safe-area sizing universal, but scope the visual color to sample content, e.g. via a CSS variable with a neutral/transparent fallback.
| position: sticky; | ||
| top: 0; | ||
| height: env(safe-area-inset-top, 0px); | ||
| background-color: rgb(3, 23, 62); |
There was a problem hiding this comment.
[major] Template safe-area behavior — This host CSS is loaded for generated apps regardless of whether sample content is included, but the dark rgb(3, 23, 62) color only matches the sample navigation chrome. With sample content disabled, Android edge-to-edge devices can still get a dark status-bar spacer over an otherwise neutral/empty app, and dark status-bar icons may be unreadable. Please make the universal rule use a neutral fallback and let sample-content CSS opt into the dark color.
Keep the sample status bar spacer color scoped to sample content and use a transparent fallback for empty template output. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
/review -b feature/refactor-copilot-yml -p android |
|
/review -b feature/refactor-copilot-yml -p android |
🤖 AI Summary
📊 Review Session —
|
| # | Source | Approach | Test Result | Files Changed | Notes |
|---|---|---|---|---|---|
| 1 | expert-review loop | Android Gate device reuse, first attempt | ❌ FAIL | 2 files | Static parse passed, but YAML edit targeted Setup env instead of Gate env. Fed into candidate 4. |
| 2 | expert-review loop | Require maui-bot author before deleting generated-marker comments |
✅ PASS | 1 file | Good posting-boundary hardening; not Android-specific. |
| 3 | expert-review loop | Strip Azure ##vso[...] commands from captured Gate output |
1 file | Parse passed; deeper dry-run blocked by unauthenticated gh. Needs broader stdout coverage before selection. |
|
| 4 | expert-review loop | Correct Android Gate DEVICE_UDID propagation and DeviceUdid binding |
✅ PASS | 2 files | Corrected candidate 1; directly addresses Android test platform behavior. |
| PR | PR #34463 | Token-scoped phased review, trusted scripts, token stripping, Post phase | 13 files | Original PR fix; no tests detected by prior gate. |
Cross-Pollination
| Model/Reviewer | Round | New Ideas? | Details |
|---|---|---|---|
| maui-expert-reviewer | 1 | Yes | Suggested trusted-runner boundary for all stages, Azure logging-command firewall, safe comment/artifact manifest, and Android device-state hardening. |
| self-review after try-fix-1 | 2 | Yes | Detected that candidate 1 patched the wrong YAML env block; generated corrected candidate 4. |
| self-review after try-fix-3 | 2 | No selected patch | Logging firewall remains valuable but validation was blocked and the candidate needs broader coverage before being considered better than the PR fix. |
Exhausted: Yes for this environment. Additional meaningful directions exist (trusted deep-UI runner boundary and manifest-rendered posting), but they require larger pipeline changes and CI validation not available without authenticated GitHub/AzDO context.
Selected Fix: Candidate #4 — it is the best tested Android-specific alternative. It fixes an existing $DeviceUdid binding gap in verify-tests-fail.ps1 and passes DEVICE_UDID to the Gate phase where Android tests actually run. Candidate #2 is also a low-risk hardening improvement, but Candidate #4 is more directly tied to the requested Android test platform.
try-fix-1 — Android device reuse (incorrect first attempt)
Approach: Bind DeviceUdid in verify-tests-fail.ps1 and pass DEVICE_UDID from the pipeline so Android Gate can reuse the emulator prepared by the pipeline instead of booting or selecting another device.
How it differs from the PR fix: The PR focuses on token/task separation and trusted scripts. This candidate addresses Android platform reliability by making device state explicit.
Test results: ❌ FAIL. PowerShell parsing passed, but review of the diff showed the pipeline variable was added to the Setup task environment, not the Gate task environment, so it did not satisfy the intended Android Gate reuse behavior.
Failure analysis: The patch matched the first GH_TOKEN env block in ci-copilot.yml. The next candidate must anchor the YAML edit to Task 2: Gate / RunGate rather than using a generic env-block match.
Diff:
diff --git a/.github/skills/verify-tests-fail-without-fix/scripts/verify-tests-fail.ps1 b/.github/skills/verify-tests-fail-without-fix/scripts/verify-tests-fail.ps1
index e54d900a1c..84894ddb36 100644
--- a/.github/skills/verify-tests-fail-without-fix/scripts/verify-tests-fail.ps1
+++ b/.github/skills/verify-tests-fail-without-fix/scripts/verify-tests-fail.ps1
@@ -49,6 +49,9 @@
(i.e., if no fix files are detected). Without this flag, the script will
automatically run in verify failure only mode when no fix files are found.
+.PARAMETER DeviceUdid
+ Device/simulator UDID to reuse for platform tests. Defaults to DEVICE_UDID.
+
.EXAMPLE
# Auto-detect everything (test type, filter, platform)
./verify-tests-fail.ps1 -Platform android
@@ -93,7 +96,10 @@ param(
[Parameter(Mandatory = $false)]
[ValidateSet("UITest", "UnitTest", "XamlUnitTest", "DeviceTest")]
- [string]$TestType
+ [string]$TestType,
+
+ [Parameter(Mandatory = $false)]
+ [string]$DeviceUdid = $env:DEVICE_UDID
)
$ErrorActionPreference = "Stop"
diff --git a/eng/pipelines/ci-copilot.yml b/eng/pipelines/ci-copilot.yml
index ebce013b91..f1346074f4 100644
--- a/eng/pipelines/ci-copilot.yml
+++ b/eng/pipelines/ci-copilot.yml
@@ -642,6 +642,7 @@ stages:
displayName: 'Task 1: Setup (branch + merge)'
env:
GH_TOKEN: $(GH_COMMENT_TOKEN)
+ DEVICE_UDID: $(DEVICE_UDID)
PARAM_PR_NUMBER: ${{ parameters.PRNumber }}
# ─────────────────────────────────────────────────────────try-fix-2 — Require bot author for generated-marker cleanup
Approach: Change stale comment cleanup so <!-- AI Summary --> and <!-- AI Gate --> marker matches only delete comments authored by maui-bot, mirroring the existing merge-conflict and try-fix cleanup behavior.
How it differs from the PR fix: The PR moves posting into a safer token-scoped phase. This candidate hardens the comment deletion boundary so user-authored comments cannot be removed just because they contain an internal marker string.
Test results: ✅ PASS. PowerShell parser validation passed for Remove-StaleMauiBotComments.ps1.
Failure analysis: None from validation. This is a small, robust hardening patch, but it does not address the Android-specific testing platform issue.
Diff:
diff --git a/.github/scripts/shared/Remove-StaleMauiBotComments.ps1 b/.github/scripts/shared/Remove-StaleMauiBotComments.ps1
index 8508014836..95c14ef1ed 100644
--- a/.github/scripts/shared/Remove-StaleMauiBotComments.ps1
+++ b/.github/scripts/shared/Remove-StaleMauiBotComments.ps1
@@ -89,12 +89,16 @@ function Remove-StaleMauiBotIssueComments {
continue
}
+ $isMauiBotComment = Test-IsMauiBotCommentAuthor $comment
+
$matchesGeneratedMarker =
- ($IncludeAISummary -and $body.Contains($script:AiSummaryCommentMarker)) -or
- ($IncludeLegacyGate -and $body.Contains($script:AiGateCommentMarker))
+ $isMauiBotComment -and (
+ ($IncludeAISummary -and $body.Contains($script:AiSummaryCommentMarker)) -or
+ ($IncludeLegacyGate -and $body.Contains($script:AiGateCommentMarker))
+ )
$matchesBotOnlyContent =
- (Test-IsMauiBotCommentAuthor $comment) -and (
+ $isMauiBotComment -and (
($IncludeMergeConflict -and (Test-IsMergeConflictCommentBody $body)) -or
($IncludeTryFix -and (Test-IsTryFixCommentBody $body))
)try-fix-3 — Azure logging-command firewall for captured Gate output
Approach: Add a helper to strip CR characters and ##vso[...] logging commands from captured Gate verification output before writing it to Azure logs.
How it differs from the PR fix: The PR strips GitHub/Copilot tokens before PR-controlled subprocesses. This candidate addresses a different boundary: preventing PR-controlled stdout from affecting Azure Pipelines state through logging commands.
Test results: Review-PR.ps1 -Phase Setup -DryRun requires authenticated gh to look up PR #34463 in this environment.
Failure analysis: The implementation is structurally plausible, but validation could not proceed beyond parse-level checks without GitHub authentication. A fuller version should apply the sanitizer to every PR-controlled stdout path and include focused Pester coverage for CRLF and ##vso[...] payloads.
Diff:
diff --git a/.github/scripts/Review-PR.ps1 b/.github/scripts/Review-PR.ps1
index c966ce7d92..bbad43eab3 100644
--- a/.github/scripts/Review-PR.ps1
+++ b/.github/scripts/Review-PR.ps1
@@ -143,6 +143,17 @@ function Invoke-WithoutGhTokens {
}
}
+function ConvertTo-SafeAzDoLogLine {
+ param([AllowNull()][object]$InputObject)
+
+ if ($null -eq $InputObject) {
+ return ''
+ }
+
+ # PR-controlled output must not be able to emit Azure Pipelines logging commands.
+ return ($InputObject.ToString() -replace "`r", '' -replace '##vso\[[^\]]*\]', '[azdo-command-removed]')
+}
+
# ─── Banner ───────────────────────────────────────────────────────────────────
Write-Host ""
Write-Host "╔═══════════════════════════════════════════════════════════╗" -ForegroundColor Cyan
@@ -1071,7 +1082,7 @@ for ($gateAttempt = 1; $gateAttempt -le $maxGateAttempts; $gateAttempt++) {
# subprocess invocations internally to strip the token before PR code runs.
$gateOutput = & pwsh -NoProfile -File "$verifyScript" -Platform $gatePlatform -PRNumber $PRNumber 2>&1
$gateExitCode = $LASTEXITCODE
- $gateOutput | ForEach-Object { Write-Host " $_" }
+ $gateOutput | ForEach-Object { Write-Host " $(ConvertTo-SafeAzDoLogLine $_)" }
# Check if this was an ENV ERROR (emulator timeout, ADB failure, etc.)
$isEnvError = $falsetry-fix-4 — Android Gate device reuse (corrected)
Approach: Bind DeviceUdid in verify-tests-fail.ps1 from the DEVICE_UDID environment variable and pass DEVICE_UDID to the Gate task specifically. This lets Android Gate reuse the emulator/device prepared by the pipeline and fixes the script's existing $DeviceUdid reference.
How it differs from the PR fix: The PR's current fix is primarily token-scoping/trusted-script architecture. This candidate is an Android-specific reliability fix for the Gate test runner path.
Test results: ✅ PASS. PowerShell parser validation passed for verify-tests-fail.ps1; YAML diff review confirmed DEVICE_UDID is added under Task 2: Gate / RunGate, not Setup.
Failure analysis: This corrected try-fix incorporates the try-fix-1 lesson by anchoring the YAML change to the Gate env block.
Diff:
diff --git a/.github/skills/verify-tests-fail-without-fix/scripts/verify-tests-fail.ps1 b/.github/skills/verify-tests-fail-without-fix/scripts/verify-tests-fail.ps1
index e54d900a1c..84894ddb36 100644
--- a/.github/skills/verify-tests-fail-without-fix/scripts/verify-tests-fail.ps1
+++ b/.github/skills/verify-tests-fail-without-fix/scripts/verify-tests-fail.ps1
@@ -49,6 +49,9 @@
(i.e., if no fix files are detected). Without this flag, the script will
automatically run in verify failure only mode when no fix files are found.
+.PARAMETER DeviceUdid
+ Device/simulator UDID to reuse for platform tests. Defaults to DEVICE_UDID.
+
.EXAMPLE
# Auto-detect everything (test type, filter, platform)
./verify-tests-fail.ps1 -Platform android
@@ -93,7 +96,10 @@ param(
[Parameter(Mandatory = $false)]
[ValidateSet("UITest", "UnitTest", "XamlUnitTest", "DeviceTest")]
- [string]$TestType
+ [string]$TestType,
+
+ [Parameter(Mandatory = $false)]
+ [string]$DeviceUdid = $env:DEVICE_UDID
)
$ErrorActionPreference = "Stop"
diff --git a/eng/pipelines/ci-copilot.yml b/eng/pipelines/ci-copilot.yml
index ebce013b91..da8bd5e7d1 100644
--- a/eng/pipelines/ci-copilot.yml
+++ b/eng/pipelines/ci-copilot.yml
@@ -672,6 +672,7 @@ stages:
displayName: 'Task 2: Gate (test verification)'
env:
GH_TOKEN: $(GH_COMMENT_TOKEN)
+ DEVICE_UDID: $(DEVICE_UDID)
PARAM_PR_NUMBER: ${{ parameters.PRNumber }}
# ─────────────────────────────────────────────────────────📋 Report — Final Recommendation
Comparative Report
Platform considered: android
Gate result: skipped because no tests were detected in this PR; gate verification was not rerun.
Candidate ranking
| Rank | Candidate | Result status | Assessment |
|---|---|---|---|
| 1 | pr-plus-reviewer |
Sandbox parse validation passed; gate skipped | Best overall. Preserves the PR's broader security architecture and incorporates the expert reviewer's three actionable fixes: Android Gate device reuse, safer stale-comment cleanup, and Azure logging-command sanitization. |
| 2 | pr |
Gate skipped; expert review found major actionable gaps | Strong baseline because it addresses the primary token/task separation problem, trusted script boundary, token stripping, and Post phase isolation, but it leaves Android device binding, comment cleanup safety, and raw Azure log output unresolved. |
| 3 | try-fix-4 |
PASS | Best standalone try-fix for the requested android platform. It correctly binds DeviceUdid and passes DEVICE_UDID to Task 2 Gate, but it only addresses one expert finding and not the PR's broader security refactor. |
| 4 | try-fix-2 |
PASS | Low-risk hardening improvement for stale comment deletion. It passed validation, but it is not Android-specific and does not address the core PR workflow/token boundary. |
| 5 | try-fix-3 |
BLOCKED | Plausible Azure logging-command hardening, and it became part of pr-plus-reviewer, but as a standalone STEP 5a candidate it was only parse-validated and deeper dry-run validation was blocked by missing GitHub authentication. |
| 6 | try-fix-1 |
FAIL | Ranked below all non-failing candidates. It attempted Android device reuse but patched the Setup env block instead of the Gate env block, so it did not satisfy the intended Android Gate behavior. |
Winner
Winner: pr-plus-reviewer
pr-plus-reviewer is the single best candidate because it keeps the raw PR's comprehensive fix for token scoping and trusted posting while addressing all three expert-review gaps. Passed try-fix candidates are valuable but narrower; the failed try-fix-1 is ranked last as required, and the blocked try-fix-3 is not selected as a standalone candidate.
The PR should also get dedicated tests or pipeline validation coverage, because the gate explicitly detected no tests in this PR.
1f8dd5f to
84345dc
Compare
16dc4f5 to
7c04ed7
Compare
- [x] Analyze the issue and identify affected files - [x] Fix `src/Templates/src/templates/maui-blazor/wwwroot/app.css` — Remove `@supports` wrapper, update background color, add env() fallbacks - [x] Fix `src/Templates/src/templates/maui-blazor-solution/MauiApp.1/wwwroot/app.css` — Same changes for the solution template variant - [x] Verify the template project builds successfully - [x] Run code review and security checks - [x] Update PR description with related issue references Fixes #34462 Fixes #33103 Fixes #14894 Related to #28986 Related to #19778 Related to #32987 Related to #32498 Related to #24694 Related to #33619 <!-- START COPILOT ORIGINAL PROMPT --> <details> <summary>Original prompt</summary> ---- *This section details on the original issue you should resolve* <issue_title>Safe area handling broken on Android and incomplete on iOS in Blazor Hybrid templates</issue_title> <issue_description>## Description The Blazor Hybrid template has safe area issues on both Android and iOS when running edge-to-edge. ### Problem 1: Android — Content renders behind status bar (portrait) On Android 15+ (API 35+), edge-to-edge rendering is enforced. The `BlazorWebView` content extends behind the system status bar, causing the top navbar/sidebar to be obscured. The template's `app.css` wraps all safe area CSS rules inside `@supports (-webkit-touch-callout: none)`, which is a WebKit-only feature query. This means the safe area rules **only apply on iOS** (Safari/WebKit WebView) and are completely ignored on Android (Chromium WebView). **Current CSS (broken on Android):** ```css .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: #f7f7f7; width: 100%; z-index: 1; } .flex-column, .navbar-brand { padding-left: env(safe-area-inset-left); } } ``` **Fix:** Remove the `@supports` wrapper and make the safe area rules universal. The `env(safe-area-inset-*)` values resolve to `0px` on platforms that don't need them, so there is no downside to applying them universally: ```css .status-bar-safe-area { display: flex; position: sticky; top: 0; height: env(safe-area-inset-top); background-color: rgb(3, 23, 62); width: 100%; z-index: 1; } .flex-column, .navbar-brand { padding-left: env(safe-area-inset-left, 0px); } ``` > **Note:** The background color `rgb(3, 23, 62)` is the effective composited color of the sidebar gradient start (`rgb(5, 39, 103)` from `MainLayout.razor.css`) combined with the `.top-row` overlay (`rgba(0,0,0,0.4)` from `NavMenu.razor.css`). The original `#f7f7f7` did not match the dark sidebar theme. Ideally the template should use a CSS variable for this so it stays in sync automatically. ### Problem 2: Android — Status bar icon color is unreadable in light mode After fixing the safe area background to the dark navy color, the status bar icons (clock, battery, signal) remain **dark/black** against the dark background in light mode, making them unreadable. Attempts to fix this via: - `styles.xml` (`windowLightStatusBar = false`) - `WindowCompat.GetInsetsController()` with `AppearanceLightStatusBars = false` in `OnCreate`/`OnPostCreate`/`OnResume` - `MauiProgram.cs` lifecycle events - Setting `UserAppTheme = AppTheme.Light` in `App.xaml` All failed because the **MAUI framework overrides `AppearanceLightStatusBars`** after all lifecycle events on API 35+. There does not appear to be a supported way to control status bar icon color when the app has a dark-colored area behind the status bar but is otherwise in light theme. ### Problem 3: iOS 26 — Symmetric landscape safe area insets prevent correct Dynamic Island handling On iOS 26 (iPhone 17 simulator), `env(safe-area-inset-left)` and `env(safe-area-inset-right)` both report **62px** in landscape mode, regardless of which side the Dynamic Island is on. This means: - When the Dynamic Island is on the **left** (landscape-right, angle 90°): padding-left 62px is correct ✅ - When the Dynamic Island is on the **right** (landscape-left, angle 270°): padding-left 62px creates unnecessary space on the left side of the sidebar ❌ **Measured values in landscape on iPhone 17 (iOS 26.2):** - `env(safe-area-inset-top)`: 0px - `env(safe-area-inset-right)`: 62px - `env(safe-area-inset-bottom)`: 20px - `env(safe-area-inset-left)`: 62px Since both left and right are always 62px, CSS alone cannot distinguish which side the island is on. A JavaScript-based approach using `screen.orientation.angle` (90° vs 270°) could conditionally apply the padding, but this adds complexity. ## Steps to Reproduce 1. Create a new app from the `MauiBlazorWebIdentity` template (or use `dotnet/blazor-samples` 10.0 sample) 2. Run the MAUI app on an Android 15+ emulator (API 35+) 3. Observe the navbar is behind the status bar in portrait 4. Run on an iPhone 17 simulator (iOS 26) and rotate to landscape 5. Observe 62px padding on both sides regardless of island position ## Expected Behavior - Portrait: Status bar area should have a background color matching the sidebar and content should not render behind system bars on any platform - Landscape: Safe area padding should only apply on the side where the hardware cutout (Dynamic Island/camera) actually is ## Environment - .NET 10 Preview - MAUI BlazorWebView - Android API 35+ (edge-to-edge enforced) - iOS 26.2 / iPhone 17 simulator - Tested on both physical characteristics via simulator rotation</issue_description> ## Comments on the Issue (you are @copilot in this secti... </details> <!-- START COPILOT CODING AGENT SUFFIX --> - Fixes #34462 <!-- START COPILOT CODING AGENT TIPS --> --- 🔒 GitHub Advanced Security automatically protects Copilot coding agent pull requests. You can protect all pull requests by enabling Advanced Security for your repositories. [Learn more about Advanced Security.](https://gh.io/cca-advanced-security) --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: mattleibow <1096616+mattleibow@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- [x] Analyze the issue and identify affected files - [x] Fix `src/Templates/src/templates/maui-blazor/wwwroot/app.css` — Remove `@supports` wrapper, update background color, add env() fallbacks - [x] Fix `src/Templates/src/templates/maui-blazor-solution/MauiApp.1/wwwroot/app.css` — Same changes for the solution template variant - [x] Verify the template project builds successfully - [x] Run code review and security checks - [x] Update PR description with related issue references Fixes #34462 Fixes #33103 Fixes #14894 Related to #28986 Related to #19778 Related to #32987 Related to #32498 Related to #24694 Related to #33619 <!-- START COPILOT ORIGINAL PROMPT --> <details> <summary>Original prompt</summary> ---- *This section details on the original issue you should resolve* <issue_title>Safe area handling broken on Android and incomplete on iOS in Blazor Hybrid templates</issue_title> <issue_description>## Description The Blazor Hybrid template has safe area issues on both Android and iOS when running edge-to-edge. ### Problem 1: Android — Content renders behind status bar (portrait) On Android 15+ (API 35+), edge-to-edge rendering is enforced. The `BlazorWebView` content extends behind the system status bar, causing the top navbar/sidebar to be obscured. The template's `app.css` wraps all safe area CSS rules inside `@supports (-webkit-touch-callout: none)`, which is a WebKit-only feature query. This means the safe area rules **only apply on iOS** (Safari/WebKit WebView) and are completely ignored on Android (Chromium WebView). **Current CSS (broken on Android):** ```css .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: #f7f7f7; width: 100%; z-index: 1; } .flex-column, .navbar-brand { padding-left: env(safe-area-inset-left); } } ``` **Fix:** Remove the `@supports` wrapper and make the safe area rules universal. The `env(safe-area-inset-*)` values resolve to `0px` on platforms that don't need them, so there is no downside to applying them universally: ```css .status-bar-safe-area { display: flex; position: sticky; top: 0; height: env(safe-area-inset-top); background-color: rgb(3, 23, 62); width: 100%; z-index: 1; } .flex-column, .navbar-brand { padding-left: env(safe-area-inset-left, 0px); } ``` > **Note:** The background color `rgb(3, 23, 62)` is the effective composited color of the sidebar gradient start (`rgb(5, 39, 103)` from `MainLayout.razor.css`) combined with the `.top-row` overlay (`rgba(0,0,0,0.4)` from `NavMenu.razor.css`). The original `#f7f7f7` did not match the dark sidebar theme. Ideally the template should use a CSS variable for this so it stays in sync automatically. ### Problem 2: Android — Status bar icon color is unreadable in light mode After fixing the safe area background to the dark navy color, the status bar icons (clock, battery, signal) remain **dark/black** against the dark background in light mode, making them unreadable. Attempts to fix this via: - `styles.xml` (`windowLightStatusBar = false`) - `WindowCompat.GetInsetsController()` with `AppearanceLightStatusBars = false` in `OnCreate`/`OnPostCreate`/`OnResume` - `MauiProgram.cs` lifecycle events - Setting `UserAppTheme = AppTheme.Light` in `App.xaml` All failed because the **MAUI framework overrides `AppearanceLightStatusBars`** after all lifecycle events on API 35+. There does not appear to be a supported way to control status bar icon color when the app has a dark-colored area behind the status bar but is otherwise in light theme. ### Problem 3: iOS 26 — Symmetric landscape safe area insets prevent correct Dynamic Island handling On iOS 26 (iPhone 17 simulator), `env(safe-area-inset-left)` and `env(safe-area-inset-right)` both report **62px** in landscape mode, regardless of which side the Dynamic Island is on. This means: - When the Dynamic Island is on the **left** (landscape-right, angle 90°): padding-left 62px is correct ✅ - When the Dynamic Island is on the **right** (landscape-left, angle 270°): padding-left 62px creates unnecessary space on the left side of the sidebar ❌ **Measured values in landscape on iPhone 17 (iOS 26.2):** - `env(safe-area-inset-top)`: 0px - `env(safe-area-inset-right)`: 62px - `env(safe-area-inset-bottom)`: 20px - `env(safe-area-inset-left)`: 62px Since both left and right are always 62px, CSS alone cannot distinguish which side the island is on. A JavaScript-based approach using `screen.orientation.angle` (90° vs 270°) could conditionally apply the padding, but this adds complexity. ## Steps to Reproduce 1. Create a new app from the `MauiBlazorWebIdentity` template (or use `dotnet/blazor-samples` 10.0 sample) 2. Run the MAUI app on an Android 15+ emulator (API 35+) 3. Observe the navbar is behind the status bar in portrait 4. Run on an iPhone 17 simulator (iOS 26) and rotate to landscape 5. Observe 62px padding on both sides regardless of island position ## Expected Behavior - Portrait: Status bar area should have a background color matching the sidebar and content should not render behind system bars on any platform - Landscape: Safe area padding should only apply on the side where the hardware cutout (Dynamic Island/camera) actually is ## Environment - .NET 10 Preview - MAUI BlazorWebView - Android API 35+ (edge-to-edge enforced) - iOS 26.2 / iPhone 17 simulator - Tested on both physical characteristics via simulator rotation</issue_description> ## Comments on the Issue (you are @copilot in this secti... </details> <!-- START COPILOT CODING AGENT SUFFIX --> - Fixes #34462 <!-- START COPILOT CODING AGENT TIPS --> --- 🔒 GitHub Advanced Security automatically protects Copilot coding agent pull requests. You can protect all pull requests by enabling Advanced Security for your repositories. [Learn more about Advanced Security.](https://gh.io/cca-advanced-security) --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: mattleibow <1096616+mattleibow@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- [x] Analyze the issue and identify affected files - [x] Fix `src/Templates/src/templates/maui-blazor/wwwroot/app.css` — Remove `@supports` wrapper, update background color, add env() fallbacks - [x] Fix `src/Templates/src/templates/maui-blazor-solution/MauiApp.1/wwwroot/app.css` — Same changes for the solution template variant - [x] Verify the template project builds successfully - [x] Run code review and security checks - [x] Update PR description with related issue references Fixes #34462 Fixes #33103 Fixes #14894 Related to #28986 Related to #19778 Related to #32987 Related to #32498 Related to #24694 Related to #33619 <!-- START COPILOT ORIGINAL PROMPT --> <details> <summary>Original prompt</summary> ---- *This section details on the original issue you should resolve* <issue_title>Safe area handling broken on Android and incomplete on iOS in Blazor Hybrid templates</issue_title> <issue_description>## Description The Blazor Hybrid template has safe area issues on both Android and iOS when running edge-to-edge. ### Problem 1: Android — Content renders behind status bar (portrait) On Android 15+ (API 35+), edge-to-edge rendering is enforced. The `BlazorWebView` content extends behind the system status bar, causing the top navbar/sidebar to be obscured. The template's `app.css` wraps all safe area CSS rules inside `@supports (-webkit-touch-callout: none)`, which is a WebKit-only feature query. This means the safe area rules **only apply on iOS** (Safari/WebKit WebView) and are completely ignored on Android (Chromium WebView). **Current CSS (broken on Android):** ```css .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: #f7f7f7; width: 100%; z-index: 1; } .flex-column, .navbar-brand { padding-left: env(safe-area-inset-left); } } ``` **Fix:** Remove the `@supports` wrapper and make the safe area rules universal. The `env(safe-area-inset-*)` values resolve to `0px` on platforms that don't need them, so there is no downside to applying them universally: ```css .status-bar-safe-area { display: flex; position: sticky; top: 0; height: env(safe-area-inset-top); background-color: rgb(3, 23, 62); width: 100%; z-index: 1; } .flex-column, .navbar-brand { padding-left: env(safe-area-inset-left, 0px); } ``` > **Note:** The background color `rgb(3, 23, 62)` is the effective composited color of the sidebar gradient start (`rgb(5, 39, 103)` from `MainLayout.razor.css`) combined with the `.top-row` overlay (`rgba(0,0,0,0.4)` from `NavMenu.razor.css`). The original `#f7f7f7` did not match the dark sidebar theme. Ideally the template should use a CSS variable for this so it stays in sync automatically. ### Problem 2: Android — Status bar icon color is unreadable in light mode After fixing the safe area background to the dark navy color, the status bar icons (clock, battery, signal) remain **dark/black** against the dark background in light mode, making them unreadable. Attempts to fix this via: - `styles.xml` (`windowLightStatusBar = false`) - `WindowCompat.GetInsetsController()` with `AppearanceLightStatusBars = false` in `OnCreate`/`OnPostCreate`/`OnResume` - `MauiProgram.cs` lifecycle events - Setting `UserAppTheme = AppTheme.Light` in `App.xaml` All failed because the **MAUI framework overrides `AppearanceLightStatusBars`** after all lifecycle events on API 35+. There does not appear to be a supported way to control status bar icon color when the app has a dark-colored area behind the status bar but is otherwise in light theme. ### Problem 3: iOS 26 — Symmetric landscape safe area insets prevent correct Dynamic Island handling On iOS 26 (iPhone 17 simulator), `env(safe-area-inset-left)` and `env(safe-area-inset-right)` both report **62px** in landscape mode, regardless of which side the Dynamic Island is on. This means: - When the Dynamic Island is on the **left** (landscape-right, angle 90°): padding-left 62px is correct ✅ - When the Dynamic Island is on the **right** (landscape-left, angle 270°): padding-left 62px creates unnecessary space on the left side of the sidebar ❌ **Measured values in landscape on iPhone 17 (iOS 26.2):** - `env(safe-area-inset-top)`: 0px - `env(safe-area-inset-right)`: 62px - `env(safe-area-inset-bottom)`: 20px - `env(safe-area-inset-left)`: 62px Since both left and right are always 62px, CSS alone cannot distinguish which side the island is on. A JavaScript-based approach using `screen.orientation.angle` (90° vs 270°) could conditionally apply the padding, but this adds complexity. ## Steps to Reproduce 1. Create a new app from the `MauiBlazorWebIdentity` template (or use `dotnet/blazor-samples` 10.0 sample) 2. Run the MAUI app on an Android 15+ emulator (API 35+) 3. Observe the navbar is behind the status bar in portrait 4. Run on an iPhone 17 simulator (iOS 26) and rotate to landscape 5. Observe 62px padding on both sides regardless of island position ## Expected Behavior - Portrait: Status bar area should have a background color matching the sidebar and content should not render behind system bars on any platform - Landscape: Safe area padding should only apply on the side where the hardware cutout (Dynamic Island/camera) actually is ## Environment - .NET 10 Preview - MAUI BlazorWebView - Android API 35+ (edge-to-edge enforced) - iOS 26.2 / iPhone 17 simulator - Tested on both physical characteristics via simulator rotation</issue_description> ## Comments on the Issue (you are @copilot in this secti... </details> <!-- START COPILOT CODING AGENT SUFFIX --> - Fixes #34462 <!-- START COPILOT CODING AGENT TIPS --> --- 🔒 GitHub Advanced Security automatically protects Copilot coding agent pull requests. You can protect all pull requests by enabling Advanced Security for your repositories. [Learn more about Advanced Security.](https://gh.io/cca-advanced-security) --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: mattleibow <1096616+mattleibow@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- [x] Analyze the issue and identify affected files - [x] Fix `src/Templates/src/templates/maui-blazor/wwwroot/app.css` — Remove `@supports` wrapper, update background color, add env() fallbacks - [x] Fix `src/Templates/src/templates/maui-blazor-solution/MauiApp.1/wwwroot/app.css` — Same changes for the solution template variant - [x] Verify the template project builds successfully - [x] Run code review and security checks - [x] Update PR description with related issue references Fixes #34462 Fixes #33103 Fixes #14894 Related to #28986 Related to #19778 Related to #32987 Related to #32498 Related to #24694 Related to #33619 <!-- START COPILOT ORIGINAL PROMPT --> <details> <summary>Original prompt</summary> ---- *This section details on the original issue you should resolve* <issue_title>Safe area handling broken on Android and incomplete on iOS in Blazor Hybrid templates</issue_title> <issue_description>## Description The Blazor Hybrid template has safe area issues on both Android and iOS when running edge-to-edge. ### Problem 1: Android — Content renders behind status bar (portrait) On Android 15+ (API 35+), edge-to-edge rendering is enforced. The `BlazorWebView` content extends behind the system status bar, causing the top navbar/sidebar to be obscured. The template's `app.css` wraps all safe area CSS rules inside `@supports (-webkit-touch-callout: none)`, which is a WebKit-only feature query. This means the safe area rules **only apply on iOS** (Safari/WebKit WebView) and are completely ignored on Android (Chromium WebView). **Current CSS (broken on Android):** ```css .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: #f7f7f7; width: 100%; z-index: 1; } .flex-column, .navbar-brand { padding-left: env(safe-area-inset-left); } } ``` **Fix:** Remove the `@supports` wrapper and make the safe area rules universal. The `env(safe-area-inset-*)` values resolve to `0px` on platforms that don't need them, so there is no downside to applying them universally: ```css .status-bar-safe-area { display: flex; position: sticky; top: 0; height: env(safe-area-inset-top); background-color: rgb(3, 23, 62); width: 100%; z-index: 1; } .flex-column, .navbar-brand { padding-left: env(safe-area-inset-left, 0px); } ``` > **Note:** The background color `rgb(3, 23, 62)` is the effective composited color of the sidebar gradient start (`rgb(5, 39, 103)` from `MainLayout.razor.css`) combined with the `.top-row` overlay (`rgba(0,0,0,0.4)` from `NavMenu.razor.css`). The original `#f7f7f7` did not match the dark sidebar theme. Ideally the template should use a CSS variable for this so it stays in sync automatically. ### Problem 2: Android — Status bar icon color is unreadable in light mode After fixing the safe area background to the dark navy color, the status bar icons (clock, battery, signal) remain **dark/black** against the dark background in light mode, making them unreadable. Attempts to fix this via: - `styles.xml` (`windowLightStatusBar = false`) - `WindowCompat.GetInsetsController()` with `AppearanceLightStatusBars = false` in `OnCreate`/`OnPostCreate`/`OnResume` - `MauiProgram.cs` lifecycle events - Setting `UserAppTheme = AppTheme.Light` in `App.xaml` All failed because the **MAUI framework overrides `AppearanceLightStatusBars`** after all lifecycle events on API 35+. There does not appear to be a supported way to control status bar icon color when the app has a dark-colored area behind the status bar but is otherwise in light theme. ### Problem 3: iOS 26 — Symmetric landscape safe area insets prevent correct Dynamic Island handling On iOS 26 (iPhone 17 simulator), `env(safe-area-inset-left)` and `env(safe-area-inset-right)` both report **62px** in landscape mode, regardless of which side the Dynamic Island is on. This means: - When the Dynamic Island is on the **left** (landscape-right, angle 90°): padding-left 62px is correct ✅ - When the Dynamic Island is on the **right** (landscape-left, angle 270°): padding-left 62px creates unnecessary space on the left side of the sidebar ❌ **Measured values in landscape on iPhone 17 (iOS 26.2):** - `env(safe-area-inset-top)`: 0px - `env(safe-area-inset-right)`: 62px - `env(safe-area-inset-bottom)`: 20px - `env(safe-area-inset-left)`: 62px Since both left and right are always 62px, CSS alone cannot distinguish which side the island is on. A JavaScript-based approach using `screen.orientation.angle` (90° vs 270°) could conditionally apply the padding, but this adds complexity. ## Steps to Reproduce 1. Create a new app from the `MauiBlazorWebIdentity` template (or use `dotnet/blazor-samples` 10.0 sample) 2. Run the MAUI app on an Android 15+ emulator (API 35+) 3. Observe the navbar is behind the status bar in portrait 4. Run on an iPhone 17 simulator (iOS 26) and rotate to landscape 5. Observe 62px padding on both sides regardless of island position ## Expected Behavior - Portrait: Status bar area should have a background color matching the sidebar and content should not render behind system bars on any platform - Landscape: Safe area padding should only apply on the side where the hardware cutout (Dynamic Island/camera) actually is ## Environment - .NET 10 Preview - MAUI BlazorWebView - Android API 35+ (edge-to-edge enforced) - iOS 26.2 / iPhone 17 simulator - Tested on both physical characteristics via simulator rotation</issue_description> ## Comments on the Issue (you are @copilot in this secti... </details> <!-- START COPILOT CODING AGENT SUFFIX --> - Fixes #34462 <!-- START COPILOT CODING AGENT TIPS --> --- 🔒 GitHub Advanced Security automatically protects Copilot coding agent pull requests. You can protect all pull requests by enabling Advanced Security for your repositories. [Learn more about Advanced Security.](https://gh.io/cca-advanced-security) --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: mattleibow <1096616+mattleibow@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- [x] Analyze the issue and identify affected files - [x] Fix `src/Templates/src/templates/maui-blazor/wwwroot/app.css` — Remove `@supports` wrapper, update background color, add env() fallbacks - [x] Fix `src/Templates/src/templates/maui-blazor-solution/MauiApp.1/wwwroot/app.css` — Same changes for the solution template variant - [x] Verify the template project builds successfully - [x] Run code review and security checks - [x] Update PR description with related issue references Fixes #34462 Fixes #33103 Fixes #14894 Related to #28986 Related to #19778 Related to #32987 Related to #32498 Related to #24694 Related to #33619 <!-- START COPILOT ORIGINAL PROMPT --> <details> <summary>Original prompt</summary> ---- *This section details on the original issue you should resolve* <issue_title>Safe area handling broken on Android and incomplete on iOS in Blazor Hybrid templates</issue_title> <issue_description>## Description The Blazor Hybrid template has safe area issues on both Android and iOS when running edge-to-edge. ### Problem 1: Android — Content renders behind status bar (portrait) On Android 15+ (API 35+), edge-to-edge rendering is enforced. The `BlazorWebView` content extends behind the system status bar, causing the top navbar/sidebar to be obscured. The template's `app.css` wraps all safe area CSS rules inside `@supports (-webkit-touch-callout: none)`, which is a WebKit-only feature query. This means the safe area rules **only apply on iOS** (Safari/WebKit WebView) and are completely ignored on Android (Chromium WebView). **Current CSS (broken on Android):** ```css .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: #f7f7f7; width: 100%; z-index: 1; } .flex-column, .navbar-brand { padding-left: env(safe-area-inset-left); } } ``` **Fix:** Remove the `@supports` wrapper and make the safe area rules universal. The `env(safe-area-inset-*)` values resolve to `0px` on platforms that don't need them, so there is no downside to applying them universally: ```css .status-bar-safe-area { display: flex; position: sticky; top: 0; height: env(safe-area-inset-top); background-color: rgb(3, 23, 62); width: 100%; z-index: 1; } .flex-column, .navbar-brand { padding-left: env(safe-area-inset-left, 0px); } ``` > **Note:** The background color `rgb(3, 23, 62)` is the effective composited color of the sidebar gradient start (`rgb(5, 39, 103)` from `MainLayout.razor.css`) combined with the `.top-row` overlay (`rgba(0,0,0,0.4)` from `NavMenu.razor.css`). The original `#f7f7f7` did not match the dark sidebar theme. Ideally the template should use a CSS variable for this so it stays in sync automatically. ### Problem 2: Android — Status bar icon color is unreadable in light mode After fixing the safe area background to the dark navy color, the status bar icons (clock, battery, signal) remain **dark/black** against the dark background in light mode, making them unreadable. Attempts to fix this via: - `styles.xml` (`windowLightStatusBar = false`) - `WindowCompat.GetInsetsController()` with `AppearanceLightStatusBars = false` in `OnCreate`/`OnPostCreate`/`OnResume` - `MauiProgram.cs` lifecycle events - Setting `UserAppTheme = AppTheme.Light` in `App.xaml` All failed because the **MAUI framework overrides `AppearanceLightStatusBars`** after all lifecycle events on API 35+. There does not appear to be a supported way to control status bar icon color when the app has a dark-colored area behind the status bar but is otherwise in light theme. ### Problem 3: iOS 26 — Symmetric landscape safe area insets prevent correct Dynamic Island handling On iOS 26 (iPhone 17 simulator), `env(safe-area-inset-left)` and `env(safe-area-inset-right)` both report **62px** in landscape mode, regardless of which side the Dynamic Island is on. This means: - When the Dynamic Island is on the **left** (landscape-right, angle 90°): padding-left 62px is correct ✅ - When the Dynamic Island is on the **right** (landscape-left, angle 270°): padding-left 62px creates unnecessary space on the left side of the sidebar ❌ **Measured values in landscape on iPhone 17 (iOS 26.2):** - `env(safe-area-inset-top)`: 0px - `env(safe-area-inset-right)`: 62px - `env(safe-area-inset-bottom)`: 20px - `env(safe-area-inset-left)`: 62px Since both left and right are always 62px, CSS alone cannot distinguish which side the island is on. A JavaScript-based approach using `screen.orientation.angle` (90° vs 270°) could conditionally apply the padding, but this adds complexity. ## Steps to Reproduce 1. Create a new app from the `MauiBlazorWebIdentity` template (or use `dotnet/blazor-samples` 10.0 sample) 2. Run the MAUI app on an Android 15+ emulator (API 35+) 3. Observe the navbar is behind the status bar in portrait 4. Run on an iPhone 17 simulator (iOS 26) and rotate to landscape 5. Observe 62px padding on both sides regardless of island position ## Expected Behavior - Portrait: Status bar area should have a background color matching the sidebar and content should not render behind system bars on any platform - Landscape: Safe area padding should only apply on the side where the hardware cutout (Dynamic Island/camera) actually is ## Environment - .NET 10 Preview - MAUI BlazorWebView - Android API 35+ (edge-to-edge enforced) - iOS 26.2 / iPhone 17 simulator - Tested on both physical characteristics via simulator rotation</issue_description> ## Comments on the Issue (you are @copilot in this secti... </details> <!-- START COPILOT CODING AGENT SUFFIX --> - Fixes #34462 <!-- START COPILOT CODING AGENT TIPS --> --- 🔒 GitHub Advanced Security automatically protects Copilot coding agent pull requests. You can protect all pull requests by enabling Advanced Security for your repositories. [Learn more about Advanced Security.](https://gh.io/cca-advanced-security) --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: mattleibow <1096616+mattleibow@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- [x] Analyze the issue and identify affected files - [x] Fix `src/Templates/src/templates/maui-blazor/wwwroot/app.css` — Remove `@supports` wrapper, update background color, add env() fallbacks - [x] Fix `src/Templates/src/templates/maui-blazor-solution/MauiApp.1/wwwroot/app.css` — Same changes for the solution template variant - [x] Verify the template project builds successfully - [x] Run code review and security checks - [x] Update PR description with related issue references Fixes #34462 Fixes #33103 Fixes #14894 Related to #28986 Related to #19778 Related to #32987 Related to #32498 Related to #24694 Related to #33619 <!-- START COPILOT ORIGINAL PROMPT --> <details> <summary>Original prompt</summary> ---- *This section details on the original issue you should resolve* <issue_title>Safe area handling broken on Android and incomplete on iOS in Blazor Hybrid templates</issue_title> <issue_description>## Description The Blazor Hybrid template has safe area issues on both Android and iOS when running edge-to-edge. ### Problem 1: Android — Content renders behind status bar (portrait) On Android 15+ (API 35+), edge-to-edge rendering is enforced. The `BlazorWebView` content extends behind the system status bar, causing the top navbar/sidebar to be obscured. The template's `app.css` wraps all safe area CSS rules inside `@supports (-webkit-touch-callout: none)`, which is a WebKit-only feature query. This means the safe area rules **only apply on iOS** (Safari/WebKit WebView) and are completely ignored on Android (Chromium WebView). **Current CSS (broken on Android):** ```css .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: #f7f7f7; width: 100%; z-index: 1; } .flex-column, .navbar-brand { padding-left: env(safe-area-inset-left); } } ``` **Fix:** Remove the `@supports` wrapper and make the safe area rules universal. The `env(safe-area-inset-*)` values resolve to `0px` on platforms that don't need them, so there is no downside to applying them universally: ```css .status-bar-safe-area { display: flex; position: sticky; top: 0; height: env(safe-area-inset-top); background-color: rgb(3, 23, 62); width: 100%; z-index: 1; } .flex-column, .navbar-brand { padding-left: env(safe-area-inset-left, 0px); } ``` > **Note:** The background color `rgb(3, 23, 62)` is the effective composited color of the sidebar gradient start (`rgb(5, 39, 103)` from `MainLayout.razor.css`) combined with the `.top-row` overlay (`rgba(0,0,0,0.4)` from `NavMenu.razor.css`). The original `#f7f7f7` did not match the dark sidebar theme. Ideally the template should use a CSS variable for this so it stays in sync automatically. ### Problem 2: Android — Status bar icon color is unreadable in light mode After fixing the safe area background to the dark navy color, the status bar icons (clock, battery, signal) remain **dark/black** against the dark background in light mode, making them unreadable. Attempts to fix this via: - `styles.xml` (`windowLightStatusBar = false`) - `WindowCompat.GetInsetsController()` with `AppearanceLightStatusBars = false` in `OnCreate`/`OnPostCreate`/`OnResume` - `MauiProgram.cs` lifecycle events - Setting `UserAppTheme = AppTheme.Light` in `App.xaml` All failed because the **MAUI framework overrides `AppearanceLightStatusBars`** after all lifecycle events on API 35+. There does not appear to be a supported way to control status bar icon color when the app has a dark-colored area behind the status bar but is otherwise in light theme. ### Problem 3: iOS 26 — Symmetric landscape safe area insets prevent correct Dynamic Island handling On iOS 26 (iPhone 17 simulator), `env(safe-area-inset-left)` and `env(safe-area-inset-right)` both report **62px** in landscape mode, regardless of which side the Dynamic Island is on. This means: - When the Dynamic Island is on the **left** (landscape-right, angle 90°): padding-left 62px is correct ✅ - When the Dynamic Island is on the **right** (landscape-left, angle 270°): padding-left 62px creates unnecessary space on the left side of the sidebar ❌ **Measured values in landscape on iPhone 17 (iOS 26.2):** - `env(safe-area-inset-top)`: 0px - `env(safe-area-inset-right)`: 62px - `env(safe-area-inset-bottom)`: 20px - `env(safe-area-inset-left)`: 62px Since both left and right are always 62px, CSS alone cannot distinguish which side the island is on. A JavaScript-based approach using `screen.orientation.angle` (90° vs 270°) could conditionally apply the padding, but this adds complexity. ## Steps to Reproduce 1. Create a new app from the `MauiBlazorWebIdentity` template (or use `dotnet/blazor-samples` 10.0 sample) 2. Run the MAUI app on an Android 15+ emulator (API 35+) 3. Observe the navbar is behind the status bar in portrait 4. Run on an iPhone 17 simulator (iOS 26) and rotate to landscape 5. Observe 62px padding on both sides regardless of island position ## Expected Behavior - Portrait: Status bar area should have a background color matching the sidebar and content should not render behind system bars on any platform - Landscape: Safe area padding should only apply on the side where the hardware cutout (Dynamic Island/camera) actually is ## Environment - .NET 10 Preview - MAUI BlazorWebView - Android API 35+ (edge-to-edge enforced) - iOS 26.2 / iPhone 17 simulator - Tested on both physical characteristics via simulator rotation</issue_description> ## Comments on the Issue (you are @copilot in this secti... </details> <!-- START COPILOT CODING AGENT SUFFIX --> - Fixes #34462 <!-- START COPILOT CODING AGENT TIPS --> --- 🔒 GitHub Advanced Security automatically protects Copilot coding agent pull requests. You can protect all pull requests by enabling Advanced Security for your repositories. [Learn more about Advanced Security.](https://gh.io/cca-advanced-security) --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: mattleibow <1096616+mattleibow@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- [x] Analyze the issue and identify affected files - [x] Fix `src/Templates/src/templates/maui-blazor/wwwroot/app.css` — Remove `@supports` wrapper, update background color, add env() fallbacks - [x] Fix `src/Templates/src/templates/maui-blazor-solution/MauiApp.1/wwwroot/app.css` — Same changes for the solution template variant - [x] Verify the template project builds successfully - [x] Run code review and security checks - [x] Update PR description with related issue references Fixes #34462 Fixes #33103 Fixes #14894 Related to #28986 Related to #19778 Related to #32987 Related to #32498 Related to #24694 Related to #33619 <!-- START COPILOT ORIGINAL PROMPT --> <details> <summary>Original prompt</summary> ---- *This section details on the original issue you should resolve* <issue_title>Safe area handling broken on Android and incomplete on iOS in Blazor Hybrid templates</issue_title> <issue_description>## Description The Blazor Hybrid template has safe area issues on both Android and iOS when running edge-to-edge. ### Problem 1: Android — Content renders behind status bar (portrait) On Android 15+ (API 35+), edge-to-edge rendering is enforced. The `BlazorWebView` content extends behind the system status bar, causing the top navbar/sidebar to be obscured. The template's `app.css` wraps all safe area CSS rules inside `@supports (-webkit-touch-callout: none)`, which is a WebKit-only feature query. This means the safe area rules **only apply on iOS** (Safari/WebKit WebView) and are completely ignored on Android (Chromium WebView). **Current CSS (broken on Android):** ```css .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: #f7f7f7; width: 100%; z-index: 1; } .flex-column, .navbar-brand { padding-left: env(safe-area-inset-left); } } ``` **Fix:** Remove the `@supports` wrapper and make the safe area rules universal. The `env(safe-area-inset-*)` values resolve to `0px` on platforms that don't need them, so there is no downside to applying them universally: ```css .status-bar-safe-area { display: flex; position: sticky; top: 0; height: env(safe-area-inset-top); background-color: rgb(3, 23, 62); width: 100%; z-index: 1; } .flex-column, .navbar-brand { padding-left: env(safe-area-inset-left, 0px); } ``` > **Note:** The background color `rgb(3, 23, 62)` is the effective composited color of the sidebar gradient start (`rgb(5, 39, 103)` from `MainLayout.razor.css`) combined with the `.top-row` overlay (`rgba(0,0,0,0.4)` from `NavMenu.razor.css`). The original `#f7f7f7` did not match the dark sidebar theme. Ideally the template should use a CSS variable for this so it stays in sync automatically. ### Problem 2: Android — Status bar icon color is unreadable in light mode After fixing the safe area background to the dark navy color, the status bar icons (clock, battery, signal) remain **dark/black** against the dark background in light mode, making them unreadable. Attempts to fix this via: - `styles.xml` (`windowLightStatusBar = false`) - `WindowCompat.GetInsetsController()` with `AppearanceLightStatusBars = false` in `OnCreate`/`OnPostCreate`/`OnResume` - `MauiProgram.cs` lifecycle events - Setting `UserAppTheme = AppTheme.Light` in `App.xaml` All failed because the **MAUI framework overrides `AppearanceLightStatusBars`** after all lifecycle events on API 35+. There does not appear to be a supported way to control status bar icon color when the app has a dark-colored area behind the status bar but is otherwise in light theme. ### Problem 3: iOS 26 — Symmetric landscape safe area insets prevent correct Dynamic Island handling On iOS 26 (iPhone 17 simulator), `env(safe-area-inset-left)` and `env(safe-area-inset-right)` both report **62px** in landscape mode, regardless of which side the Dynamic Island is on. This means: - When the Dynamic Island is on the **left** (landscape-right, angle 90°): padding-left 62px is correct ✅ - When the Dynamic Island is on the **right** (landscape-left, angle 270°): padding-left 62px creates unnecessary space on the left side of the sidebar ❌ **Measured values in landscape on iPhone 17 (iOS 26.2):** - `env(safe-area-inset-top)`: 0px - `env(safe-area-inset-right)`: 62px - `env(safe-area-inset-bottom)`: 20px - `env(safe-area-inset-left)`: 62px Since both left and right are always 62px, CSS alone cannot distinguish which side the island is on. A JavaScript-based approach using `screen.orientation.angle` (90° vs 270°) could conditionally apply the padding, but this adds complexity. ## Steps to Reproduce 1. Create a new app from the `MauiBlazorWebIdentity` template (or use `dotnet/blazor-samples` 10.0 sample) 2. Run the MAUI app on an Android 15+ emulator (API 35+) 3. Observe the navbar is behind the status bar in portrait 4. Run on an iPhone 17 simulator (iOS 26) and rotate to landscape 5. Observe 62px padding on both sides regardless of island position ## Expected Behavior - Portrait: Status bar area should have a background color matching the sidebar and content should not render behind system bars on any platform - Landscape: Safe area padding should only apply on the side where the hardware cutout (Dynamic Island/camera) actually is ## Environment - .NET 10 Preview - MAUI BlazorWebView - Android API 35+ (edge-to-edge enforced) - iOS 26.2 / iPhone 17 simulator - Tested on both physical characteristics via simulator rotation</issue_description> ## Comments on the Issue (you are @copilot in this secti... </details> <!-- START COPILOT CODING AGENT SUFFIX --> - Fixes #34462 <!-- START COPILOT CODING AGENT TIPS --> --- 🔒 GitHub Advanced Security automatically protects Copilot coding agent pull requests. You can protect all pull requests by enabling Advanced Security for your repositories. [Learn more about Advanced Security.](https://gh.io/cca-advanced-security) --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: mattleibow <1096616+mattleibow@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- [x] Analyze the issue and identify affected files - [x] Fix `src/Templates/src/templates/maui-blazor/wwwroot/app.css` — Remove `@supports` wrapper, update background color, add env() fallbacks - [x] Fix `src/Templates/src/templates/maui-blazor-solution/MauiApp.1/wwwroot/app.css` — Same changes for the solution template variant - [x] Verify the template project builds successfully - [x] Run code review and security checks - [x] Update PR description with related issue references Fixes #34462 Fixes #33103 Fixes #14894 Related to #28986 Related to #19778 Related to #32987 Related to #32498 Related to #24694 Related to #33619 <!-- START COPILOT ORIGINAL PROMPT --> <details> <summary>Original prompt</summary> ---- *This section details on the original issue you should resolve* <issue_title>Safe area handling broken on Android and incomplete on iOS in Blazor Hybrid templates</issue_title> <issue_description>## Description The Blazor Hybrid template has safe area issues on both Android and iOS when running edge-to-edge. ### Problem 1: Android — Content renders behind status bar (portrait) On Android 15+ (API 35+), edge-to-edge rendering is enforced. The `BlazorWebView` content extends behind the system status bar, causing the top navbar/sidebar to be obscured. The template's `app.css` wraps all safe area CSS rules inside `@supports (-webkit-touch-callout: none)`, which is a WebKit-only feature query. This means the safe area rules **only apply on iOS** (Safari/WebKit WebView) and are completely ignored on Android (Chromium WebView). **Current CSS (broken on Android):** ```css .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: #f7f7f7; width: 100%; z-index: 1; } .flex-column, .navbar-brand { padding-left: env(safe-area-inset-left); } } ``` **Fix:** Remove the `@supports` wrapper and make the safe area rules universal. The `env(safe-area-inset-*)` values resolve to `0px` on platforms that don't need them, so there is no downside to applying them universally: ```css .status-bar-safe-area { display: flex; position: sticky; top: 0; height: env(safe-area-inset-top); background-color: rgb(3, 23, 62); width: 100%; z-index: 1; } .flex-column, .navbar-brand { padding-left: env(safe-area-inset-left, 0px); } ``` > **Note:** The background color `rgb(3, 23, 62)` is the effective composited color of the sidebar gradient start (`rgb(5, 39, 103)` from `MainLayout.razor.css`) combined with the `.top-row` overlay (`rgba(0,0,0,0.4)` from `NavMenu.razor.css`). The original `#f7f7f7` did not match the dark sidebar theme. Ideally the template should use a CSS variable for this so it stays in sync automatically. ### Problem 2: Android — Status bar icon color is unreadable in light mode After fixing the safe area background to the dark navy color, the status bar icons (clock, battery, signal) remain **dark/black** against the dark background in light mode, making them unreadable. Attempts to fix this via: - `styles.xml` (`windowLightStatusBar = false`) - `WindowCompat.GetInsetsController()` with `AppearanceLightStatusBars = false` in `OnCreate`/`OnPostCreate`/`OnResume` - `MauiProgram.cs` lifecycle events - Setting `UserAppTheme = AppTheme.Light` in `App.xaml` All failed because the **MAUI framework overrides `AppearanceLightStatusBars`** after all lifecycle events on API 35+. There does not appear to be a supported way to control status bar icon color when the app has a dark-colored area behind the status bar but is otherwise in light theme. ### Problem 3: iOS 26 — Symmetric landscape safe area insets prevent correct Dynamic Island handling On iOS 26 (iPhone 17 simulator), `env(safe-area-inset-left)` and `env(safe-area-inset-right)` both report **62px** in landscape mode, regardless of which side the Dynamic Island is on. This means: - When the Dynamic Island is on the **left** (landscape-right, angle 90°): padding-left 62px is correct ✅ - When the Dynamic Island is on the **right** (landscape-left, angle 270°): padding-left 62px creates unnecessary space on the left side of the sidebar ❌ **Measured values in landscape on iPhone 17 (iOS 26.2):** - `env(safe-area-inset-top)`: 0px - `env(safe-area-inset-right)`: 62px - `env(safe-area-inset-bottom)`: 20px - `env(safe-area-inset-left)`: 62px Since both left and right are always 62px, CSS alone cannot distinguish which side the island is on. A JavaScript-based approach using `screen.orientation.angle` (90° vs 270°) could conditionally apply the padding, but this adds complexity. ## Steps to Reproduce 1. Create a new app from the `MauiBlazorWebIdentity` template (or use `dotnet/blazor-samples` 10.0 sample) 2. Run the MAUI app on an Android 15+ emulator (API 35+) 3. Observe the navbar is behind the status bar in portrait 4. Run on an iPhone 17 simulator (iOS 26) and rotate to landscape 5. Observe 62px padding on both sides regardless of island position ## Expected Behavior - Portrait: Status bar area should have a background color matching the sidebar and content should not render behind system bars on any platform - Landscape: Safe area padding should only apply on the side where the hardware cutout (Dynamic Island/camera) actually is ## Environment - .NET 10 Preview - MAUI BlazorWebView - Android API 35+ (edge-to-edge enforced) - iOS 26.2 / iPhone 17 simulator - Tested on both physical characteristics via simulator rotation</issue_description> ## Comments on the Issue (you are @copilot in this secti... </details> <!-- START COPILOT CODING AGENT SUFFIX --> - Fixes #34462 <!-- START COPILOT CODING AGENT TIPS --> --- 🔒 GitHub Advanced Security automatically protects Copilot coding agent pull requests. You can protect all pull requests by enabling Advanced Security for your repositories. [Learn more about Advanced Security.](https://gh.io/cca-advanced-security) --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: mattleibow <1096616+mattleibow@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
src/Templates/src/templates/maui-blazor/wwwroot/app.css— Remove@supportswrapper, update background color, add env() fallbackssrc/Templates/src/templates/maui-blazor-solution/MauiApp.1/wwwroot/app.css— Same changes for the solution template variantFixes #34462
Fixes #33103
Fixes #14894
Related to #28986
Related to #19778
Related to #32987
Related to #32498
Related to #24694
Related to #33619
Original prompt
This section details on the original issue you should resolve
<issue_title>Safe area handling broken on Android and incomplete on iOS in Blazor Hybrid templates</issue_title>
<issue_description>## Description
The Blazor Hybrid template has safe area issues on both Android and iOS when running edge-to-edge.
Problem 1: Android — Content renders behind status bar (portrait)
On Android 15+ (API 35+), edge-to-edge rendering is enforced. The
BlazorWebViewcontent extends behind the system status bar, causing the top navbar/sidebar to be obscured.The template's
app.csswraps all safe area CSS rules inside@supports (-webkit-touch-callout: none), which is a WebKit-only feature query. This means the safe area rules only apply on iOS (Safari/WebKit WebView) and are completely ignored on Android (Chromium WebView).Current CSS (broken on Android):
Fix: Remove the
@supportswrapper and make the safe area rules universal. Theenv(safe-area-inset-*)values resolve to0pxon platforms that don't need them, so there is no downside to applying them universally:Problem 2: Android — Status bar icon color is unreadable in light mode
After fixing the safe area background to the dark navy color, the status bar icons (clock, battery, signal) remain dark/black against the dark background in light mode, making them unreadable.
Attempts to fix this via:
styles.xml(windowLightStatusBar = false)WindowCompat.GetInsetsController()withAppearanceLightStatusBars = falseinOnCreate/OnPostCreate/OnResumeMauiProgram.cslifecycle eventsUserAppTheme = AppTheme.LightinApp.xamlAll failed because the MAUI framework overrides
AppearanceLightStatusBarsafter all lifecycle events on API 35+. There does not appear to be a supported way to control status bar icon color when the app has a dark-colored area behind the status bar but is otherwise in light theme.Problem 3: iOS 26 — Symmetric landscape safe area insets prevent correct Dynamic Island handling
On iOS 26 (iPhone 17 simulator),
env(safe-area-inset-left)andenv(safe-area-inset-right)both report 62px in landscape mode, regardless of which side the Dynamic Island is on. This means:Measured values in landscape on iPhone 17 (iOS 26.2):
env(safe-area-inset-top): 0pxenv(safe-area-inset-right): 62pxenv(safe-area-inset-bottom): 20pxenv(safe-area-inset-left): 62pxSince both left and right are always 62px, CSS alone cannot distinguish which side the island is on. A JavaScript-based approach using
screen.orientation.angle(90° vs 270°) could conditionally apply the padding, but this adds complexity.Steps to Reproduce
MauiBlazorWebIdentitytemplate (or usedotnet/blazor-samples10.0 sample)Expected Behavior
Environment
Comments on the Issue (you are @copilot in this secti...
🔒 GitHub Advanced Security automatically protects Copilot coding agent pull requests. You can protect all pull requests by enabling Advanced Security for your repositories. Learn more about Advanced Security.