Skip to content

fix: resolve issues with safe area / systembars plugin - #8535

Merged
theproducer merged 17 commits into
ionic-team:mainfrom
tafelnl:fix/resolve-issues-with-safe-area
Sep 10, 2026
Merged

theproducer merged 17 commits into
ionic-team:mainfrom
tafelnl:fix/resolve-issues-with-safe-area

Conversation

@tafelnl

@tafelnl tafelnl commented Jul 21, 2026 •

Copy link
Copy Markdown
Contributor

This resolves some (most/all maybe even) current issues with the safe area / systembars plugin. These changes have been battle tested in production for several months now.

Closes #8525
Closes #8528
Closes #8416
Closes ionic-team/capacitor-keyboard#68
Closes ionic-team/capacitor-keyboard#61
Closes ionic-team/capacitor-keyboard#46
Closes ionic-team/capacitor-keyboard#28
Closes ionic-team/capacitor-keyboard#53
Closes ionic-team/capacitor-keyboard#57

And potentially some more. I haven't had time to look at all issues.

We already had a conversation about some of these design approaches earlier on. See #8268 and #8384 (comment)

Comment thread core/system-bars.md
if (isSafeAreaPluginPresent()) {
Logger.warn(
"SystemBars",
"You should uninstall `@capacitor-community/safe-area`. Having this library installed can lead to unexpected behavior."

@tafelnl tafelnl Jul 21, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having this warning here seems fair to me, as the two are strictly incompatible and there are still loads of users installing the community plugin

@tafelnl tafelnl Jul 27, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ended up removing this warning. Because, actually, I think it makes more sense to solve this using an updating peerDependencies policy in the plugin itself. Nonetheless it might be good to mention somewhere in the docs that the use of (any) third party safe area plugins is discouraged. And if a developer wants to use such a plugin, that insetsHandling should be set to disable to prevent interference

.build();
}

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {

@tafelnl tafelnl Jul 21, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm still not sure what this if-statement was meant for. I asked about it a few times in other PRs but never got a response, so I dont know the rationale behind it. Seems unnecessary to me. Moreover, it seems to be the root cause of many bug reports

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There were some internal requirements we had to adhere to. Its not necessary anymore.

Comment thread cli/src/declarations.ts Outdated
*
* @default false
*/
initialViewportFitCover?: boolean;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one is really important to prevent layout jumps

@tafelnl tafelnl Jul 27, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ended up renaming this to initialViewportFitValueHint (in 7abde6f) because it sounds a bit more logical

if (!systemBarsInsetsHandling.equals("disable") && keyboardResizeOnFullScreen) {
Logger.warn(
"SystemBars",
"You should omit `Keyboard.resizeOnFullScreen` in your `capacitor.config.json`. Other values can lead to unexpected behavior."

@tafelnl tafelnl Jul 21, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I mentioned before, I think this config value should be deprecated altogether. If you still want it, I think it makes much more sense to include it in this plugin to prevent future bugs and resolve present bugs

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We've had discussions about this internally, and we agree. We'll get to it eventually. For now, this warning will do.

Comment on lines -243 to -244
Insets safeAreaInsets = calcSafeAreaInsets(newInsets);
injectSafeAreaCSS(safeAreaInsets.top, safeAreaInsets.right, safeAreaInsets.bottom, safeAreaInsets.left);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I simplified this, because it was doing redundant recalculations which caused bugs across different webview versions

@Vissie2

Vissie2 commented Jul 22, 2026

Copy link
Copy Markdown

@theproducer

@tafelnl tafelnl Jul 24, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We might want to call EdgeToEdge.enable(this) inside BridgeActivity.onCreate (if insetsHandling !== 'disable'). Because it's easy to forget. See #8522 for example.

However, one could argue that it's breaking change. In that case, it should then be documented somewhere, and instead be added in Capacitor v9.

On the other hand, your app will express unexpected behavior when not calling EdgeToEdge.enable(this), and there already were quite a few breaking changes in non-major updates in Capacitor v8 regarding the safe area. So it might make sense to add it nonetheless

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to reduce the amount of churn and potential for upstream breaking changes for some of the other teams depending on Capacitor 8 currently, I'll just make note of this in the docs. We'll make all of this default in Cap 9.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good!

@tafelnl
tafelnl force-pushed the fix/resolve-issues-with-safe-area branch from 7abde6f to 9b35343 Compare August 4, 2026 14:22
@tafelnl
tafelnl force-pushed the fix/resolve-issues-with-safe-area branch from 7b2d246 to 4d9996b Compare August 4, 2026 14:25
@riderx

riderx commented Aug 13, 2026

Copy link
Copy Markdown

you can try to make a patch with https://capgo.app/docs/plugins/capacitor-patch/ to use it and get it or https://github.com/Cap-go/capacitor-plus merged to capacitor

Comment thread cli/src/declarations.ts
riderx pushed a commit to Cap-go/capacitor-plus that referenced this pull request Aug 26, 2026
cursor Bot pushed a commit to Cap-go/capacitor-plus that referenced this pull request Aug 27, 2026
…rite

Upstream ionic-team#8535 removed navBarVisible state; tests that reflected on the
private field caused NoSuchFieldException in test-android. Keep hide/show
WindowInsetsController coverage.

Co-authored-by: Martin DONADIEU <martindonadieu@gmail.com>
riderx pushed a commit to Cap-go/capacitor-plus that referenced this pull request Aug 27, 2026
cursor Bot pushed a commit to Cap-go/capacitor-plus that referenced this pull request Aug 27, 2026
Co-authored-by: Martin DONADIEU <martindonadieu@gmail.com>
riderx pushed a commit to Cap-go/capacitor-plus that referenced this pull request Aug 28, 2026
@theproducer
theproducer changed the base branch from main to next September 8, 2026 20:49
@theproducer
theproducer changed the base branch from next to main September 8, 2026 21:22

@theproducer theproducer left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for consolidating and drilling down these edge to edge bugs!

if (!systemBarsInsetsHandling.equals("disable") && keyboardResizeOnFullScreen) {
Logger.warn(
"SystemBars",
"You should omit `Keyboard.resizeOnFullScreen` in your `capacitor.config.json`. Other values can lead to unexpected behavior."

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We've had discussions about this internally, and we agree. We'll get to it eventually. For now, this warning will do.

Comment thread core/system-bars.md
.build();
}

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There were some internal requirements we had to adhere to. Its not necessary anymore.

@tafelnl

tafelnl commented Sep 10, 2026 •

Copy link
Copy Markdown
Contributor Author

Thanks for consolidating and drilling down these edge to edge bugs!

🎉 you're welcome!

And thank you for taking the time to review it and making it happen. Appreciated!

@theproducer
theproducer merged commit e37d9c6 into ionic-team:main Sep 10, 2026
6 checks passed
fabiomartino added a commit to fabiomartino/capacitor that referenced this pull request Sep 13, 2026
Merge upstream 8.5.2 (5e0f678) into the safe-area/edge-to-edge PR branch.

Integrates upstream ionic-team#8535 (safe area / systembars fixes) while keeping the
branch architecture (legacy API 21-34 support, margins + CSS variables,
keyboard via CSS, no window padding):

- Add 'native' insetsHandling mode: safe area handling delegated to the
  platform/WebView, insets passed through untouched, no CSS variables
  injected, and setDecorFitsSystemWindows(true) is no longer forced so
  apps can opt into native edge-to-edge (env(safe-area-inset-*)).
- Add 'initialViewportFitValueHint' config to pre-seed hasViewportCover
  and prevent layout shifts on first paint.
- Adopt single-webViewListener registration (prevents duplicate
  listeners across start cycles) gated on 'disable'.
- Move viewport-fit detection from the removed @JavascriptInterface
  onDOMReady() into onPageCommitVisible (drop CapacitorSystemBarsAndroidInterface).
- Add warnAboutUnsupportedConfigurationValues() for Keyboard.resizeOnFullScreen.
- Keep: requested-style storage (DEFAULT follows system theme), nav bar
  resource-height fallback for API<30, pre-Oreo IME visibility handling,
  cached WebView major version with NumberFormatException safety, hardened
  CSS variable injection (removeProperty + duplicate cleanup).

Validation: compileReleaseJavaWithJavac, lintRelease, testRelease pass.
@seravifer

seravifer commented Sep 15, 2026 •

Copy link
Copy Markdown

Hi, after upgrading to 8.5.2 we hit a regression that we traced to the listener move in this PR, filed as #8603.

On API < 30, attaching the OnApplyWindowInsetsListener to the decor view exposes it to the window-level SYSTEM_UI_FLAG_LAYOUT_* flags that androidx EdgeToEdge.enable() sets. LAYOUT_HIDE_NAVIGATION drops the navigation bar from the window's system-window insets, so insets.getInsets(systemBars() | displayCutout()).bottom arrives as 0 while the navigation bar is visible, and web content renders underneath it. The top inset is unaffected, and API 30+ is fine because WindowInsetsCompat there uses the real WindowInsets.getInsets(Type) API.

Measured on a PAX A920Pro (Android 10, WebView 147): env(safe-area-inset-bottom) = 0 with a 48dp navigation bar drawn over the window.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment