@capacitor-community/safe-area
v7.0.0-beta.8
Published
Capacitor Plugin that patches the safe area for older versions of Chromium
Maintainers
Readme
Introduction
On web and iOS the safe area insets work perfectly fine out of the box1. That is, on these platforms the env(safe-area-inset-*) CSS variables will have the correct values by default. On Android (in combination with Capacitor), however, those CSS variables will not always have the correct values when Edge-to-Edge mode is enabled. So we need to work some magic in order to achieve the desired behavior. This plugin does that by detecting the Chromium version a user has installed. If a user has a Chromium version lower than 140, this plugin makes sure the webview gets the safe area as a padding. The env(safe-area-inset-*) values will be set to 0px. That means for the Chromium webview versions with a bug, the developer doesn't have to worry about safe area insets at all. For all other versions, the developer should handle the safe area insets just as he would on web or iOS (by using the env(safe-area-inset-*) CSS variables). In short you can think of this plugin as a polyfill for older webview versions.
[!NOTE]
1 As with all (regular) web applications, you still need to tell the browser to scale the viewport as so to fill the device display by setting a viewport meta value like so:
<meta name="viewport" content="viewport-fit=cover" />More info on the Mozilla website about setting the
<meta name="viewport">and using CSS env().
Installation
npm install @capacitor-community/safe-area@beta
npx cap syncUsage
The plugin itself is enabled automatically. Additionally, you should enable native edge-to-edge mode in your MainActivity like so:
Java:
public class MainActivity extends BridgeActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this); // enable edge-to-edge mode
}
}Kotlin:
class MainActivity : BridgeActivity() {
public override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge() // enable edge-to-edge mode
}
}Additionally, this plugin provides helper methods to style the system bars and its content. You can find the API docs here.
Setup
This plugin might conflict with some of your existing setup. Please go through the following steps, to ensure proper functioning of this plugin.
If you're installing this plugin into a fresh Capacitor setup, you can probably skip most - if not all - of these steps.
@capacitor/keyboard
The keyboard plugin by Capacitor has a configuration prop called resizeOnFullScreen. You should either omit it or set it to false. Otherwise it would interfere with the logic in this plugin. The bug that the Capacitor team is trying to fix with that prop is already accounted for in this plugin. So you should be good to go a don't worry about it at all.
@capacitor/status-bar
When using @capacitor-community/safe-area, you should uninstall @capacitor/status-bar. Instead you can use the System Bars API this plugin provides.
adjustMarginsForEdgeToEdge setting
Capacitor provides a setting called adjustMarginsForEdgeToEdge. It's advised to either omit this value or set it to disable to prevent interference. This plugin already does a similar thing when it detects a broken webview.
windowOptOutEdgeToEdgeEnforcement
If you've set windowOptOutEdgeToEdgeEnforcement in your AndroidManifest.xml, you should probably remove it. It has been deprecated and shouldn't be necessary when using this plugin anyways.
Extending BridgeWebViewClient and calling setWebViewClient
If you're running Capacitor v7 and have extended the BridgeWebViewClient by calling bridge.setWebViewClient in your code, you should update your code so that it extends SafeAreaWebViewClient instead. In Capacitor v8 this can and will be worked around another way, so you shouldn't have to worry about that anymore.
Other safe area plugins
If you've installed any other safe area plugin, you should remove them to prevent interference.
Examples of these are: @capawesome/capacitor-android-edge-to-edge-support, capacitor-plugin-safe-area and @aashu-dubey/capacitor-statusbar-safe-area.
Just make sure to uninstall those and you should be good to go.
Earlier versions of this plugin
Alpha versions of this plugin (@capacitor-community/safe-area@alpha) are deprecated and usage of those versions is advised against. Please migrate to a beta or latest channel instead. Differences between the older versions and the newer versions are outlined here.
Capacitor v8
As of this writing, Capacitor v8 has just been released. But if you're already using it, you should set the following in your capacitor.config.json:
{
"plugins": {
"SystemBars": {
"insetsHandling": "disable"
}
}
}Differences between this plugin, system-bars and status-bar
The main differences are as follows:
| | @capacitor-community/safe-area | CapacitorSystemBars5 | @capacitor/status-bar |
| ----------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- |
| Supported Capacitor versions | ✅ v7+ | ⚠️ v8+ | ✅ v7+ |
| Helper methods for styling and hiding the system bars | ✅ | ✅ | ✅ |
| Updates styling of system bars upon config changes | ✅ | ✅ | ✅ |
| Enables Edge to Edge functionality | ✅ | ✅ | ❌ |
| Option to opt out of handling insets for Android | ✅ | ❌ | N/A |
| Philosophy2 | ✅ Native approach. Aligns with iOS and web behavior. Does not require extra work. Fallback to padding for broken webviews. | ⚠️ All or nothing approach using custom CSS vars injection. Requires extra work (migrating from envs to vars). Doesn't distinguish between broken and fixed webview versions. It's arguably more prone to future bugs. | N/A |
| Fallback supported on Android versions | ✅ Android 6+ | ❌ Android 15+ (broken behavior on older versions) | N/A |
| Workaround for resizing keyboard bug3 | ✅ Working out of the box | ⚠️ Requires installing @capacitor/keyboard and setting resizeOnFullScreen. Their workaround also still has bugs | ❌ |
| Workaround for inset keyboard bug4 | ✅ Working out of the box | ❌ | N/A |
| Detects viewport-fit=cover changes | ✅ Works for any arbitrary web content | ⚠️ Works only for web content that has Capacitor v8 set up | N/A |
2 Chromium versions < 140 do not correctly report safe area insets. So we need a workaround for those webviews. This can be done using padding or custom CSS vars. This plugin advocates for using padding. As it seems to be the least breaking behavior.
3 The webview has a known bug to not resize the webview when the keyboard is shown.
4 The webview has another known bug to not properly report bottom insets when the keyboard is shown. Which will be fixed in Chromium 144
5 The Capacitor team themselves are also working on a similar
solution by means of a CapacitorSystemBars plugin. It's still a
work in progress though. Currently that PR is merged as is and in a beta state. Their design/approach philosophy also
deviates from the one in this plugin.
FAQ
Do I need this plugin?
You probably do! Apps targeting Android sdk version 36 will automatically be in edge to edge mode on a device running Android 16+. This means you should properly support safe area insets. This seemingly works just fine. However Chromium versions < 140 have a bug that causes the webview to receive incorrect values (0px). This plugin works around that by adding a padding to the webview for those devices. As a bonus, this plugin resizes the webview upon keyboard visibility changes, something that also isn't standard.
Why doesn't Capacitor take care of this out of the box?
They can and they should. They tried / are trying actually. But IMHO their approach isn't ready for production (yet). The differences between their approach and the one of this plugin are outlined here. I try to adhere to their coding style and I am actively providing feedback to the Capacitor team in hopes they will - sometime - upstream this plugin into their core codebase.
I don't care about safe area insets or edge to edge functionality!
That's not a question ;) And your users might care about them though! So it's recommended to support them. But if you really do not want to support edge to edge, you've got two options:
- Remove the
viewport-fit=covermeta tag if you've set that. Mind you that this removes edge to edge support entirely, for all platforms (Android, iOS and web) - If you want to only remove support for Android, set the following value in your
capacitor.config.json:
This effectively makes this plugin to only add padding around the webview on all of your Android devices. And disables detecting the{ "plugins": { "SafeArea": { "detectViewportFitCoverChanges": false, "initialViewportFitCover": false } } }viewport-fitvalue and disables passing through insets (because they're already consumed by the padding).
System Bars API
This plugin provides a few helper methods for styling the system bars. It's designed to be a partial drop-in replacement for the @capacitor/status-bar plugin. It doesn't support a few things, like setOverlaysWebView for example, as those aren't applicable when handling the safe areas using insets.
[!NOTE] On iOS this API requires "View controller-based status bar appearance" (
UIViewControllerBasedStatusBarAppearance) set toYESinInfo.plist. Read about Configuring iOS for help.The status bar visibility defaults to visible and the style defaults to
UIStatusBarStyle.default. You can change these defaults by addingUIStatusBarHiddenand/orUIStatusBarStyleinInfo.plist.
setSystemBarsStyle(...)
setSystemBarsStyle(options: SystemBarsStyleOptions) => Promise<void>| Param | Type |
| ------------- | ------------------------------------------------------------------------- |
| options | SystemBarsStyleOptions |
showSystemBars(...)
showSystemBars(options: SystemBarsVisibilityOptions) => Promise<void>| Param | Type |
| ------------- | ----------------------------------------------------------------------------------- |
| options | SystemBarsVisibilityOptions |
hideSystemBars(...)
hideSystemBars(options: SystemBarsVisibilityOptions) => Promise<void>| Param | Type |
| ------------- | ----------------------------------------------------------------------------------- |
| options | SystemBarsVisibilityOptions |
Interfaces
SystemBarsStyleOptions
| Prop | Type | Description | Default |
| ----------- | ----------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------- |
| style | SystemBarsStyle | Style of the content of the system bars. | 'DEFAULT' |
| type | SystemBarsType | The system bar to which to apply the style. Providing null means it will be applied to both system bars. On iOS the home indicator cannot be styled. It will always automatically be applied a color by iOS out of the box. | null |
SystemBarsVisibilityOptions
| Prop | Type | Description | Default |
| ---------- | --------------------------------------------------------- | --------------------------------------------------------------------------------------- | ----------------- |
| type | SystemBarsType | The system bar to hide or show. Providing null means it will toggle both system bars. | null |
Enums
SystemBarsStyle
| Members | Value | Description |
| ------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Dark | 'DARK' | Light system bar content on a dark background. |
| Light | 'LIGHT' | For dark system bar content on a light background. |
| Default | 'DEFAULT' | The style is based on the device appearance or the underlying content. If the device is using dark mode, the system bars content will be light. If the device is using light mode, the system bars content will be dark. |
SystemBarsType
| Members | Value | Description |
| ------------------- | ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| StatusBar | 'STATUS_BAR' | The top status bar on both Android and iOS. |
| NavigationBar | 'NAVIGATION_BAR' | The navigation bar on both Android and iOS. On iOS this is the "home indicator". On Android this is either the "navigation bar" or the "gesture bar". |
Config
| Prop | Type | Description | Default |
| ----------------------------------- | ----------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------- |
| statusBarStyle | SystemBarsStyle | Indicates which style to apply to the status bar initially. | null |
| navigationBarStyle | SystemBarsStyle | Indicates which style to apply to the navigation bar initially. On iOS the home indicator cannot be styled. It will always automatically be applied a color by iOS out of the box. | null |
| detectViewportFitCoverChanges | boolean | This plugin detects changes to the viewport-fit=cover meta tag. This comes in handy when you do not know for sure if the content loaded into the webview will have viewport-fit set to cover. For most use cases you do not need to touch this config variable. However if you know for sure you want to always keep the initialViewportFitCover value unchanged, you could disable this feature by setting it to false. Be aware that this might result in a visually broken UI if the content loaded into the webview does not correctly handle safe area insets. This option is only supported on Android. | true |
| initialViewportFitCover | boolean | Set an initial value for the to be detected viewport-fit=cover. For most apps this value will eventually be true. Therefore this value is set to true by default to help prevent layout jumps and glitches. If you know (or want) the value to be false initially, you can set it here. The value will always end up correctly, no matter what you set here, as long as detectViewportFitCoverChanges is set to true. It only exists to help prevent layout jumps and glitches. This option is only supported on Android. | true |
| offsetForKeyboardInsetBug | boolean | | |
Examples
In capacitor.config.json:
{
"plugins": {
"SafeArea": {
"statusBarStyle": undefined,
"navigationBarStyle": undefined,
"detectViewportFitCoverChanges": undefined,
"initialViewportFitCover": undefined,
"offsetForKeyboardInsetBug": undefined
}
}
}In capacitor.config.ts:
/// <reference types="@capacitor-community/safe-area" />
import { CapacitorConfig } from '@capacitor/cli';
const config: CapacitorConfig = {
plugins: {
SafeArea: {
statusBarStyle: undefined,
navigationBarStyle: undefined,
detectViewportFitCoverChanges: undefined,
initialViewportFitCover: undefined,
offsetForKeyboardInsetBug: undefined,
},
},
};
export default config;