Title: Translucent ion-header/ion-toolbar measures --ion-safe-area-top exactly once and never re-measures, breaking on platforms where the real value arrives asynchronously (Android edge-to-edge)
Prerequisites
Ionic Info
Ionic:
Ionic CLI : (not relevant, using @ionic/angular directly)
@ionic/angular : 8.8.16
Capacitor:
@capacitor/android : 8.5.0
@capacitor/core : 8.5.0
Platform: Android (Capacitor), targetSdk 35+/36, edge-to-edge (mandatory on this OS version, no opt-out).
Current Behavior
A translucent <ion-header [translucent]="true"> with a matching <ion-content [fullscreen]="true"> measures the app's --ion-safe-area-top CSS custom property exactly once, at component initialization, and caches whatever value it read at that moment. If the real value only becomes known after that initial measurement — which is unavoidable on Android, where the true status-bar inset is only available asynchronously via a native WindowInsets callback dispatched after Activity.onCreate() — the header permanently renders as if the safe area were 0, even once the CSS variable is later updated to the correct value.
Confirmed via a runtime probe logging both the CSS variable and the toolbar's measured position over time:
t0: --ion-safe-area-top=0px toolbarTop=n/a
t100ms: --ion-safe-area-top=24px toolbarTop=n/a
t500ms: --ion-safe-area-top=24px toolbarTop=0
t1500ms: --ion-safe-area-top=24px toolbarTop=0
t3000ms: --ion-safe-area-top=24px toolbarTop=0
The CSS variable is correctly updated to 24px by ~100ms and stays correct — but the toolbar's own layout never reflects it, at any point afterward, including 3 seconds later.
Expected Behavior
The header should react to changes in --ion-safe-area-top (and the other --ion-safe-area-* variables) after initialization — e.g. via a MutationObserver on the CSS custom property, or by re-measuring on window resize/orientation events, rather than caching a one-time JS measurement. This would make translucent headers correctly handle any platform/runtime where the true safe-area value isn't synchronously available at component init — which, going forward, includes Android, now that edge-to-edge is OS-mandatory there (Android 15+) and Capacitor's WebView doesn't populate env(safe-area-inset-*) for the general status bar (only for display cutouts — a separate, narrower case that iOS's WKWebView doesn't distinguish this way).
Steps to Reproduce
- Capacitor Android app, targetSdk 35+.
- Native side: apply the real status-bar inset to the WebView late (e.g. via a
WindowInsets listener registered on the Activity's decor view, since — separately — listening directly on the WebView itself receives all-zero insets on Capacitor; see companion issue filed against capacitor-android), pushing the correct value into --ion-safe-area-top via document.documentElement.style.setProperty(...) once it's known.
- Observe: even though the CSS variable is now correct, any
[translucent]="true" ion-header present at that point continues to render as if it were 0 — content still appears under the status bar.
- A plain (non-translucent) header, or a completely fresh page navigated to after the value is corrected, is unaffected — only headers that existed at/before the mismeasurement stay wrong, consistent with a cached one-time read rather than a live calculation.
Code Reproduction Link
Happy to put together a minimal Capacitor+Ionic Angular repro if useful — the behavior above was observed in a real app, not yet isolated into a standalone reproduction repo.
Ionic Framework Info
Framework: Angular (@ionic/angular 8.8.16)
Capacitor: 8.5.0
Platform: Android, targetSdk 36
Title: Translucent
ion-header/ion-toolbarmeasures--ion-safe-area-topexactly once and never re-measures, breaking on platforms where the real value arrives asynchronously (Android edge-to-edge)Prerequisites
Ionic Info
Platform: Android (Capacitor), targetSdk 35+/36, edge-to-edge (mandatory on this OS version, no opt-out).
Current Behavior
A translucent
<ion-header [translucent]="true">with a matching<ion-content [fullscreen]="true">measures the app's--ion-safe-area-topCSS custom property exactly once, at component initialization, and caches whatever value it read at that moment. If the real value only becomes known after that initial measurement — which is unavoidable on Android, where the true status-bar inset is only available asynchronously via a nativeWindowInsetscallback dispatched afterActivity.onCreate()— the header permanently renders as if the safe area were0, even once the CSS variable is later updated to the correct value.Confirmed via a runtime probe logging both the CSS variable and the toolbar's measured position over time:
The CSS variable is correctly updated to
24pxby ~100ms and stays correct — but the toolbar's own layout never reflects it, at any point afterward, including 3 seconds later.Expected Behavior
The header should react to changes in
--ion-safe-area-top(and the other--ion-safe-area-*variables) after initialization — e.g. via aMutationObserveron the CSS custom property, or by re-measuring on window resize/orientation events, rather than caching a one-time JS measurement. This would make translucent headers correctly handle any platform/runtime where the true safe-area value isn't synchronously available at component init — which, going forward, includes Android, now that edge-to-edge is OS-mandatory there (Android 15+) and Capacitor'sWebViewdoesn't populateenv(safe-area-inset-*)for the general status bar (only for display cutouts — a separate, narrower case that iOS's WKWebView doesn't distinguish this way).Steps to Reproduce
WindowInsetslistener registered on the Activity's decor view, since — separately — listening directly on the WebView itself receives all-zero insets on Capacitor; see companion issue filed againstcapacitor-android), pushing the correct value into--ion-safe-area-topviadocument.documentElement.style.setProperty(...)once it's known.[translucent]="true"ion-headerpresent at that point continues to render as if it were0— content still appears under the status bar.Code Reproduction Link
Happy to put together a minimal Capacitor+Ionic Angular repro if useful — the behavior above was observed in a real app, not yet isolated into a standalone reproduction repo.
Ionic Framework Info