
safe-area-handling
Fixes notch, Dynamic Island, home indicator, display cutout, and system bar overlap in Capacitor apps. Covers viewport-fit=cover + env(safe-area-inset-), the Capacitor 8 SystemBars core plugin (insetsHandling, injected --safe-area-inset- CSS variables for Android WebView < 140, removal of android.adjustMarginsForEdgeToEdge), Android 15/16 enforced edge-to-edge (overlaysWebView and backgroundColor of @capacitor/status-bar no longer work at targetSdk 36), iOS contentInset, and insets that change at runtime on rotation, iPad/foldable resizable windows. Use when headers sit under the status bar, tab bars hide behind the gesture bar, env() returns 0 on Android, or layout broke after upgrading to Capacitor 8 / targetSdk 35+. Do not use for keyboard overlap (capacitor-keyboard), Ionic/Konsta component theming (ionic-design, konsta-ui), Tailwind utility setup (tailwind-capacitor), or iOS multi-window/resizability adoption (capacitor-ios-resizability).
- Installs
- 1.3K
- Last 24h
- —
- Trending rank
- —
- Security
- Not audited
Install
Works with every agent the skills CLI supports.
npx skills add https://github.com/cap-go/capgo-skills --skill safe-area-handling- 01
Install
Run the command in your project. The CLI asks which agents to install for.
- 02
Describe the task
Ask your agent for something this skill covers. It loads safe-area-handling automatically when relevant.
- 03
Review the result
The agent follows the SKILL.md steps, scripts and references bundled with the skill.
SKILL.md
The instructions your agent reads when the skill is triggered.
Safe Area Handling in Capacitor
When to Use
TRIGGER when:
- Content renders under the status bar, notch, Dynamic Island, cutout, home indicator, or Android navigation/gesture bar
env(safe-area-inset-*)is0pxon Android, or correct only after rotation- Layout broke after Capacitor 8 upgrade or raising
targetSdkVersionto 35/36 StatusBar.setOverlaysWebView/setBackgroundColorstopped working on Android- Landscape / split-screen / iPad windowed layouts have wrong side padding
Do not use:
- Keyboard covering inputs:
capacitor-keyboard - Ionic
ion-header/ion-toolbartheming:ionic-design; KonstasafeAreas:konsta-ui - Tailwind plugin/utility setup:
tailwind-capacitor - iOS scene sizing,
UIScreen.main, multi-window:capacitor-ios-resizability - Splash screen colors:
capacitor-splash-screen
Facts that drive every decision
| Platform / version | Behavior |
|---|---|
| iOS (all) | WebView is full-screen. env(safe-area-inset-*) works only with viewport-fit=cover. ios.contentInset defaults to never. |
| Android, Capacitor <= 7 | android.adjustMarginsForEdgeToEdge config could add native margins. |
| Android, Capacitor 8+ | adjustMarginsForEdgeToEdge removed. SystemBars core plugin (in @capacitor/core) handles insets. |
| Android 15 (targetSdk 35) | Edge-to-edge enforced; opt-out via windowOptOutEdgeToEdgeEnforcement still possible. |
| Android 16 (targetSdk 36, Capacitor 8 default; 37 on Capacitor 9) | Opt-out ignored. @capacitor/status-bar overlaysWebView and backgroundColor have no effect. |
| Android WebView < 140 | env(safe-area-inset-*) wrong/0 (Chromium bug). SystemBars injects --safe-area-inset-* variables (default insetsHandling: 'css'). |
How SystemBars behaves on Android (from core source): if the page has viewport-fit=cover and WebView >= 140, insets pass through to env() and the WebView is edge-to-edge. Otherwise Capacitor pads the WebView natively so content sits between the bars and env() reports 0. --safe-area-inset-* variables carry correct values in both cases when insetsHandling is css.
Workflow
- Inspect:
index.htmlviewport meta,capacitor.config.*(ios.contentInset,android.adjustMarginsForEdgeToEdge,plugins.SystemBars,plugins.StatusBar),@capacitor/core+@capacitor/status-barversions,android/variables.gradletargetSdkVersion, global CSS that setspadding-top, UI framework (Ionic / Konsta handle insets themselves). - Decide the design: full-bleed (draw under bars, pad content) vs. contained (content between bars). Ask the user if mockups are unclear.
- Full-bleed (recommended):
viewport-fit=cover+ the CSS fallback chain below, applied once at the layout shell, not per component. - Remove legacy config: delete
android.adjustMarginsForEdgeToEdge; drop Android uses ofStatusBar.setOverlaysWebView/setBackgroundColor(keep for iOS if wanted); preferSystemBars.setStylefor icon color. - Framework check: Ionic already applies
--ion-safe-area-*; Konsta usessafeAreas. Do not double-pad. - Verify on devices (below).
Only load a reference when its topic is in play:
- references/android-edge-to-edge.md - SystemBars config, status-bar migration, nav bar color, WebView version checks
- references/css-patterns.md - layout shell, tab bars, reading insets in JS, debug overlay, resizable windows
Baseline
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
:root {
--sat: var(--safe-area-inset-top, env(safe-area-inset-top, 0px));
--sar: var(--safe-area-inset-right, env(safe-area-inset-right, 0px));
--sab: var(--safe-area-inset-bottom, env(safe-area-inset-bottom, 0px));
--sal: var(--safe-area-inset-left, env(safe-area-inset-left, 0px));
}
.app-header { padding-top: var(--sat); padding-inline: var(--sal) var(--sar); }
.app-tabbar { padding-bottom: var(--sab); padding-inline: var(--sal) var(--sar); }
The --safe-area-inset-* names are injected by Capacitor's SystemBars on Android; on iOS and web they are undefined, so the env() fallback applies.
import { SystemBars, SystemBarsStyle } from '@capacitor/core';
await SystemBars.setStyle({ style: SystemBarsStyle.Dark }); // light icons on dark background
SystemBarsStyle.Dark = light content for dark backgrounds; Light = dark content. iOS needs UIViewControllerBasedStatusBarAppearance = YES (template default).
Traps
- Missing
viewport-fit=coveris the #1 cause ofenv()= 0 on iOS and of Capacitor padding the WebView on Android. - Do not combine
ios.contentInset: 'always'with CSS safe-area padding: insets apply twice. - Do not hardcode 20/44/47/59 px status bar heights; heights differ per device, orientation, and window size.
- Insets change at runtime (rotation, iPad windowed mode, foldables, Android split screen, keyboard). Use CSS, or re-read on
resize; never cache once at startup. - On iOS the bottom inset stays while the keyboard is visible; use
max()with keyboard height (seecapacitor-keyboard). 100vhincludes the area under system bars; use100dvhor flex layout with a fixed shell.- Ionic:
ion-contentwithfullscreen+ion-headeralready pads; adding body padding shifts everything twice.
Verification
npx cap sync, run on: iPhone with Dynamic Island, iPhone SE-class (home button, small top inset), iPad (rotate + Stage Manager/windowed), Android 15/16 device or emulator in gesture and 3-button navigation, landscape.- Check Android WebView version:
adb shell dumpsys package com.google.android.webview | grep versionName(< 140 relies on injected variables). - In Safari Web Inspector /
chrome://inspect, run:
getComputedStyle(document.documentElement).getPropertyValue('--sat');
document.querySelector('meta[name=viewport]').content.includes('viewport-fit=cover');
- Confirm no element overlaps bars; tab bar taps work above the gesture bar; no double padding.
- Grep for leftovers:
grep -rn "adjustMarginsForEdgeToEdge\|setOverlaysWebView\|setBackgroundColor\|windowOptOutEdgeToEdgeEnforcement" capacitor.config.* src android/app/src/main/res 2>/dev/null
Error Handling
| Symptom / message | Cause | Fix |
|---|---|---|
Header under status bar on iOS, env() = 0 | No viewport-fit=cover | Add to viewport meta |
Android: content correct but no edge-to-edge, env() = 0 | No viewport-fit=cover or WebView < 140, so Capacitor pads natively | Add viewport-fit=cover; use --safe-area-inset-* fallback |
| Android: double gap at top | Native padding + your CSS padding with insetsHandling: 'disable' or custom MainActivity insets code | Remove custom setDecorFitsSystemWindows / inset listeners; keep SystemBars default |
StatusBar.setBackgroundColor / setOverlaysWebView no effect on Android | targetSdk 35+/36 edge-to-edge | Draw a background under the bar in CSS; use SystemBars.setStyle for icon color |
android.adjustMarginsForEdgeToEdge still in config after Capacitor 8 upgrade, margins gone | Option removed and ignored | Delete it; rely on SystemBars + CSS |
Logcat Unknown insetsHandling value '...'. Falling back to 'css'. | Invalid SystemBars.insetsHandling | Use a value listed in the current docs (css, disable; newer cores also accept native) |
| Status bar text invisible (white on white) | Style mismatch | SystemBars.setStyle({ style: SystemBarsStyle.Light }) |
| Layout jumps on first paint (Android) | SystemBars detects viewport-fit after page commit | Set plugins.SystemBars.initialViewportFitValueHint: 'cover' (in core config declarations; verify in your version) |
Resources
- System Bars API: https://capacitorjs.com/docs/apis/system-bars
- Status Bar API (Android 16 note): https://capacitorjs.com/docs/apis/status-bar
- Updating to 8.0: https://capacitorjs.com/docs/updating/8-0
- Android edge-to-edge: https://developer.android.com/develop/ui/views/layout/edge-to-edge
- WebKit viewport-fit / env(): https://webkit.org/blog/7929/designing-websites-for-iphone-x/
FAQ
What does the safe-area-handling skill do?
Fixes notch, Dynamic Island, home indicator, display cutout, and system bar overlap in Capacitor apps. Covers viewport-fit=cover + env(safe-area-inset-), the Capacitor 8 SystemBars core plugin (insetsHandling, injected --safe-area-inset- CSS variables for Android WebView < 140, removal of android.adjustMarginsForEdgeToEdge), Android 15/16 enforced edge-to-edge (overlaysWebView and backgroundColor of @capacitor/status-bar no longer work at targetSdk 36), iOS contentInset, and insets that change at runtime on rotation, iPad/foldable resizable windows. Use when headers sit under the status bar, tab bars hide behind the gesture bar, env() returns 0 on Android, or layout broke after upgrading to Capacitor 8 / targetSdk 35+. Do not use for keyboard overlap (capacitor-keyboard), Ionic/Konsta component theming (ionic-design, konsta-ui), Tailwind utility setup (tailwind-capacitor), or iOS multi-window/resizability adoption (capacitor-ios-resizability).
How do I install safe-area-handling?
Run npx skills add https://github.com/cap-go/capgo-skills --skill safe-area-handling in your project. The skills CLI detects the agents you use and copies the skill into the right folder. For Claude Code you can also place the skill folder at ~/.claude/skills/safe-area-handling/ (personal) or .claude/skills/safe-area-handling/ (project).
Which AI agents can use safe-area-handling?
Any agent that supports the open Agent Skills format (a folder with a SKILL.md file), including Claude Code, Codex, Cursor, GitHub Copilot, Gemini CLI, Windsurf, Cline, OpenCode and more.
Is safe-area-handling safe to install?
No partner security audit is available yet. Skills can run commands and scripts, so read the SKILL.md and any bundled files before installing.
Who maintains safe-area-handling?
It is published by cap-go in cap-go/capgo-skills. It has 1,302 recorded installs.
More from cap-go/capgo-skills
capacitor-best-practices
cap-go/capgo-skills
Review and set up Capacitor apps against current best practices (Capacitor 8.5 stable, 9 alpha via @next). Use for a Capacitor codebase review, capacitor.config.ts audit, new project setup, aligning @capacitor/core/cli/ios/android and plugin majors, plugin patterns (availability checks, checkPermissions/requestPermissions, error code handling, web fallbacks), or release prep; or on " " plugin is not implemented on ios, Could not find the web assets directory, platform has not been added yet, a dev server.url shipped to production, lost edits in android/app/src/main/assets/public, SPM vs CocoaPods, or AppDelegate/UIScene drift. Do not use for security audits (capacitor-security), performance (capacitor-performance), tests (capacitor-testing), crashes (debugging-capacitor), major upgrades (capacitor-app-upgrades), SPM migration (cocoapods-to-spm), UIScene adoption (capacitor-uiscene-migration), or live updates (capgo-live-updates).
ionic-design
cap-go/capgo-skills
Builds and reviews Ionic Framework UI in Capacitor apps (Ionic 9 current, Ionic 8 still common) - page structure (ion-header/ion-content/ion-footer), lists, modern form syntax (label/labelPlacement on ion-input/ion-select), sheet modals, tabs + ion-router-outlet, CSS variable theming, dark palettes (palettes/dark.system.css, .ion-palette-dark), iOS vs MD mode, and Ionic 8 - 9 breaking changes (React Router 6, Vue Router 5, Angular 18-22 zoneless, removed ion-picker-legacy, boolean autocorrect, npx @ionic/migrate). Use when writing Ionic components, theming an Ionic app, fixing platform-mode styling, or upgrading @ionic/react, @ionic/vue, @ionic/angular. Do not use for Konsta UI (konsta-ui), Tailwind setup (tailwind-capacitor), raw safe-area CSS (safe-area-handling), keyboard resize (capacitor-keyboard), or Ionic Appflow/Enterprise migrations (ionic-appflow-migration, ionic-enterprise-sdk-migration).
capacitor-plugins
cap-go/capgo-skills
Pick, install, and wire up Capacitor plugins for native features (camera, filesystem, geolocation, biometrics, in-app purchases, social login, SQLite, calendar, contacts, health, BLE, NFC, etc.). Covers official @capacitor/ packages (Capacitor 8.5 stable, 9 alpha on next) plus the 145-package @capgo/ index, native permission keys (Info.plist, AndroidManifest, variables.gradle minSdk), npx cap sync / cap ls verification, and errors like '"Camera" plugin is not implemented on ios', 'unable to find plugin', UNIMPLEMENTED, and OS-PLUG- codes. Use for "which plugin should I use for X", adding a plugin, or a plugin call failing after install. Do not use for push setup (capacitor-push-notifications), splash assets (capacitor-splash-screen), keyboard layout (capacitor-keyboard), deep links (capacitor-deep-linking), OTA updates (capgo-live-updates), upgrading plugin source code (capacitor-plugin-upgrades), or adding SPM to a plugin (capacitor-plugin-spm-support).
debugging-capacitor
cap-go/capgo-skills
Diagnoses and fixes runtime bugs in Capacitor iOS/Android apps - white/blank screen on launch, startup crashes, "X" plugin is not implemented on ios/android, net::ERRCLEARTEXTNOTPERMITTED, App Transport Security blocks, CORS on capacitor://localhost, permission denials, live reload not connecting, and Capacitor 8.5+ UIScene regressions (appUrlOpen, universal links, pause/resume silent because AppDelegate callbacks stopped). Covers Safari Web Inspector, chrome://inspect, Xcode/LLDB, Android Studio, npx cap run --url (Capacitor 9) vs -l --host (8), xcrun simctl openurlprivacypush, xcrun devicectl device process launchopenURL, xctrace. Do not use for only reading device logs (ios-android-logs), the UIScene migration itself (capacitor-uiscene-migration), deep-link setup (capacitor-deep-linking), speed (capacitor-performance), crash SDKs (capacitor-native-observability), tests (capacitor-testing), or upgrades/build errors (capacitor-app-upgrades, cocoapods-to-spm).
capacitor-testing
cap-go/capgo-skills
Testing guide for Capacitor apps and plugins. Covers web-layer unit and component tests with Vitest and mocked Capacitor plugins (vi.mock('@capacitor/core'), registerPlugin), Playwright for the web build, Appium E2E with NATIVEAPP / WEBVIEW context switching, native plugin unit tests with Swift Testing vs XCTest (what can migrate, CAPPluginCall patterns, xcodebuild test on a simulator) and Android JUnit, plus an agent-driven verification loop on simulators and devices (build, install, launch, screenshot, UI hierarchy, tap, logs) with xcrun simctl, xcrun devicectl, adb, Xcode MCP device tools, and live reload via npx cap run --url (Capacitor 9) or -l --host --port (Capacitor 8). Use when adding tests, mocking plugins, "does it work on device", verifying a UI change on a simulator, migrating XCTest to Swift Testing, or setting up test CI. Do not use for crash log capture (ios-android-logs), WebView debugging sessions (debugging-capacitor), or build pipelines (capacitor
capacitor-deep-linking
cap-go/capgo-skills
Implements and debugs deep links in Capacitor apps - custom URL schemes, iOS Universal Links (apple-app-site-association, Associated Domains entitlement), Android App Links (assetlinks.json, autoVerify), and routing via @capacitor/app appUrlOpen + App.getLaunchUrl(). Covers the Capacitor 8.5+ UIScene change where AppDelegate application(:open:options:) and continue userActivity stop being called and custom handling must move to SceneDelegate, plus cold vs warm launch testing. Use when links open Safari/Chrome instead of the app, appUrlOpen never fires, the launch URL is lost on cold start, or OAuth redirects do not return to the app. Do not use for the full UIScene migration (capacitor-uiscene-migration), push notification taps (capacitor-push-notifications), or social login plugin setup (capacitor-plugins).
Popular mobile skills
design-mobile-apps
designed-by-ai/skills
Design mobile app screens with Sleek, edit Sleek projects, and implement their designs in React Native or HTML.
ios-design
uizze.sh
Design and review iOS UI/UX for iPhone apps in SwiftUI, UIKit, or React Native. Use for mobile app design, screen redesigns, navigation flows, typography, forms, keyboard and safe-area layout, and accessibility checks. Apply Apple Human Interface Guidelines and consult real product references through the optional Uizze connection.
vercel-react-native-skills
vercel-labs/agent-skills
React Native and Expo best practices for building performant mobile apps. Use when building React Native components, optimizing list performance, implementing animations, or working with native modules. Triggers on tasks involving React Native, Expo, mobile performance, or native platform APIs.
animate-expo
emilkowalski/skills
Build animations in React Native and Expo, making the decisions in the order that determines whether they feel right — should it animate, which thread it runs on, which properties, spring or timing, how the gesture hands off, how it degrades. Writes the implementation with Reanimated, Gesture Handler, Expo Router and expo-haptics. Use when animating anything in an Expo app, adding gestures, sheets, screen transitions, press feedback or haptics, or fixing motion that stutters on device. For web animation use animate.
xcode-project-setup
firebase/agent-skills
Safely modifies Xcode projects (.pbxproj) to add Swift Packages and link files. Use this skill whenever an iOS project needs dependencies installed (e.g. Firebase, Alamofire).
firebase-remote-config-basics
firebase/agent-skills
Manages Firebase Remote Config templates, feature flags, loading strategies, and SDKs (Android, iOS). Use when downloading/deploying remoteconfig JSON templates, managing version history/feature flags, setting in-app defaults, fetchAndActivate(), real-time listeners, or SDK setup. Don't use for Firebase Hosting, Auth, Firestore, Data Connect, Crashlytics, or App Hosting.