Skip to main content
safe-area-handling Complete guide to handling safe areas in Capacitor apps for iPhone notch, Dynamic Island, home indicator, and Android cutouts. Covers CSS, JavaScript, and native solutions. Use this skill when users have layout issues on modern devices.
Ir a la instalación Skills Marketplace Descubre y explora habilidades de IA creadas por la comunidad.
Instalar con Codex o Claude Copia este prompt, pégalo en Codex, Claude u otro asistente, y deja que revise la página de la skill y la instale por ti.
Copiar promptMostrar detalles del prompt Un comando directo omite el prompt de revisión. Revisa el origen antes de ejecutarlo.
npx skills add https://github.com/Cap-go/capgo-skills --skill safe-area-handlingEl comando permanece en una sola línea. Desplázate horizontalmente para revisarlo antes de copiarlo.
¿Prefieres una copia local? Descarga los archivos que SkillsMP tiene disponibles ahora.
Descargar Zip Descargando... Explorador de archivos
2 archivos Ocupaciones relacionadas SOC
Basado en la clasificación ocupacional SOC
Más de este repositorio capawesome-live-update-migration Guides migration from Capawesome Cloud live updates or @capawesome/capacitor-live-update to Capgo Updater. Use when a Capacitor app contains Capawesome live update packages, CLI commands, config, API calls, or when the user asks why Capgo Updater is the better live-update path: native updater runtime, fully open source, cheaper at comparable scale, and longer proven track record.
Official Capacitor package guide plus Capgo ecosystem plugin recommendations. Use this skill when users need native functionality, want the right official Capacitor package, or need a stronger Capgo/community plugin when the official package is missing or too limited.
Use for Capgo Cloud Build native iOS and Android workflows, including CLI login, API-key handling, iOS build onboarding, signing credential storage, build requests, store upload settings, output download links, and troubleshooting. Do not use for OTA bundle uploads or generic Capacitor setup unless a native Capgo build is requested.
name safe-area-handling description Complete guide to handling safe areas in Capacitor apps for iPhone notch, Dynamic Island, home indicator, and Android cutouts. Covers CSS, JavaScript, and native solutions. Use this skill when users have layout issues on modern devices.
Safe Area Handling in Capacitor
Handle iPhone notch, Dynamic Island, home indicator, and Android cutouts properly.
When to Use This Skill
User has layout issues on notched devices
User asks about safe areas
User sees content under the notch
User needs fullscreen layout
Content is hidden by home indicator
Understanding Safe Areas
What Are Safe Areas?
Safe areas are the regions of the screen not obscured by:
iPhone : Notch, Dynamic Island, home indicator, rounded corners
Android : Camera cutouts, navigation gestures, display cutouts
Safe Area Insets
Inset Description safe-area-inset-topNotch/Dynamic Island/status bar safe-area-inset-bottomHome indicator/navigation bar safe-area-inset-leftLeft edge (landscape) safe-area-inset-rightRight edge (landscape)
CSS Solution
Enable Viewport Coverage
<meta
name ="viewport"
=
/>
content
"width=device-width, initial-scale=1.0, viewport-fit=cover"
Important : viewport-fit=cover is required to access safe area insets.
Using CSS Environment Variables
.header {
padding-top : env (safe-area-inset-top);
}
.footer {
padding-bottom : env (safe-area-inset-bottom);
}
.header {
padding-top : env (safe-area-inset-top, 20px );
}
.content {
padding-top : calc (env (safe-area-inset-top) + 16px );
padding-bottom : calc (env (safe-area-inset-bottom) + 16px );
}
Full Page Layout
.app {
position : fixed;
top : 0 ;
left : 0 ;
right : 0 ;
bottom : 0 ;
display : flex;
flex-direction : column;
}
.header {
padding-top : env (safe-area-inset-top);
padding-left : env (safe-area-inset-left);
padding-right : env (safe-area-inset-right);
background : #fff ;
}
.content {
flex : 1 ;
overflow-y : auto;
-webkit-overflow -scrolling: touch;
padding-left : env (safe-area-inset-left);
padding-right : env (safe-area-inset-right);
}
.footer {
padding-bottom : env (safe-area-inset-bottom);
padding-left : env (safe-area-inset-left);
padding-right : env (safe-area-inset-right);
background : #fff ;
}
Tab Bar with Safe Area .tab-bar {
position : fixed;
bottom : 0 ;
left : 0 ;
right : 0 ;
display : flex;
background : #fff ;
border-top : 1px solid #eee ;
padding-bottom : env (safe-area-inset-bottom);
}
.tab-bar-item {
flex : 1 ;
display : flex;
flex-direction : column;
align-items : center;
padding : 8px 0 ;
min-height : 49px ;
}
Full-Bleed Background with Safe Content .hero {
background : linear-gradient (to bottom, #4f46e5 , #7c3aed );
padding-top : calc (env (safe-area-inset-top) + 20px );
padding-left : env (safe-area-inset-left);
padding-right : env (safe-area-inset-right);
}
.hero-content {
max-width : 100% ;
}
JavaScript Solution
Reading Safe Area Values function getSafeAreaInsets ( ) {
const computedStyle = getComputedStyle (document .documentElement );
return {
top : parseInt (computedStyle.getPropertyValue ('--sat' ) || '0' ),
bottom : parseInt (computedStyle.getPropertyValue ('--sab' ) || '0' ),
left : parseInt (computedStyle.getPropertyValue ('--sal' ) || '0' ),
right : parseInt (computedStyle.getPropertyValue ('--sar' ) || '0' ),
};
}
function setSafeAreaProperties ( ) {
const style = document .documentElement .style ;
const temp = document .createElement ('div' );
temp.style .paddingTop = 'env(safe-area-inset-top)' ;
temp.style .paddingBottom = 'env(safe-area-inset-bottom)' ;
temp.style .paddingLeft = 'env(safe-area-inset-left)' ;
temp.style .paddingRight = 'env(safe-area-inset-right)' ;
document .body .appendChild (temp);
const computed = getComputedStyle (temp);
style.setProperty ('--sat' , computed.paddingTop );
style.setProperty ('--sab' , computed.paddingBottom );
style.setProperty ('--sal' , computed.paddingLeft );
style.setProperty ('--sar' , computed.paddingRight );
document .body .removeChild (temp);
}
window .addEventListener ('orientationchange' , () => {
setTimeout (setSafeAreaProperties, 100 );
});
React Hook import { useState, useEffect } from 'react' ;
interface SafeAreaInsets {
top : number ;
bottom : number ;
left : number ;
right : number ;
}
function useSafeArea ( ): SafeAreaInsets {
const [insets, setInsets] = useState<SafeAreaInsets >({
top : 0 ,
bottom : 0 ,
left : 0 ,
right : 0 ,
});
useEffect (() => {
function updateInsets ( ) {
const temp = document .createElement ('div' );
temp.style .cssText = `
position: fixed;
top: 0;
padding-top: env(safe-area-inset-top);
padding-bottom: env(safe-area-inset-bottom);
padding-left: env(safe-area-inset-left);
padding-right: env(safe-area-inset-right);
` ;
document .body .appendChild (temp);
const computed = getComputedStyle (temp);
setInsets ({
top : parseFloat (computed.paddingTop ) || 0 ,
bottom : parseFloat (computed.paddingBottom ) || 0 ,
left : parseFloat (computed.paddingLeft ) || 0 ,
right : parseFloat (computed.paddingRight ) || 0 ,
});
document .body .removeChild (temp);
}
const handleOrientationChange = ( ) => {
setTimeout (updateInsets, 100 );
};
updateInsets ();
window .addEventListener ('resize' , updateInsets);
window .addEventListener ('orientationchange' , handleOrientationChange);
return () => {
window .removeEventListener ('resize' , updateInsets);
window .removeEventListener ('orientationchange' , handleOrientationChange);
};
}, []);
return insets;
}
function Header ( ) {
const { top } = useSafeArea ();
return (
<header style ={{ paddingTop: top }}>
App Header
</header >
);
}
Vue Composable import { ref, onMounted, onUnmounted } from 'vue' ;
export function useSafeArea ( ) {
const insets = ref ({
top : 0 ,
bottom : 0 ,
left : 0 ,
right : 0 ,
});
function updateInsets ( ) {
const temp = document .createElement ('div' );
temp.style .cssText = `
position: fixed;
padding-top: env(safe-area-inset-top);
padding-bottom: env(safe-area-inset-bottom);
padding-left: env(safe-area-inset-left);
padding-right: env(safe-area-inset-right);
` ;
document .body .appendChild (temp);
const computed = getComputedStyle (temp);
insets.value = {
top : parseFloat (computed.paddingTop ) || 0 ,
bottom : parseFloat (computed.paddingBottom ) || 0 ,
left : parseFloat (computed.paddingLeft ) || 0 ,
right : parseFloat (computed.paddingRight ) || 0 ,
};
document .body .removeChild (temp);
}
onMounted (() => {
updateInsets ();
window .addEventListener ('resize' , updateInsets);
});
onUnmounted (() => {
window .removeEventListener ('resize' , updateInsets);
});
return insets;
}
Native iOS Configuration
Status Bar Style
import type { CapacitorConfig } from '@capacitor/cli' ;
const config : CapacitorConfig = {
ios : {
contentInset : 'automatic' ,
},
};
Extend Behind Safe Areas
import UIKit
import Capacitor
@UIApplicationMain
class AppDelegate : UIResponder , UIApplicationDelegate {
func application (
_ application : UIApplication ,
didFinishLaunchingWithOptions launchOptions : [UIApplication .LaunchOptionsKey : Any ]?
) -> Bool {
if let window = UIApplication .shared.windows.first {
window.backgroundColor = .clear
}
return true
}
}
Info.plist Settings
<key > UIViewControllerBasedStatusBarAppearance</key >
<true />
<key > UISupportedInterfaceOrientations</key >
<array >
<string > UIInterfaceOrientationPortrait</string >
<string > UIInterfaceOrientationLandscapeLeft</string >
<string > UIInterfaceOrientationLandscapeRight</string >
</array >
Native Android Configuration
Display Cutout Mode
<resources >
<style name ="AppTheme" parent ="Theme.AppCompat.NoActionBar" >
<item name ="android:windowLayoutInDisplayCutoutMode" > shortEdges</item >
</style >
</resources >
Edge-to-Edge Display
import android.os.Build
import android.view.View
import android.view.WindowInsets
import android.view.WindowInsetsController
class MainActivity : BridgeActivity () {
override fun onCreate (savedInstanceState: Bundle ?) {
super .onCreate(savedInstanceState)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
window.setDecorFitsSystemWindows(false )
} else {
@Suppress("DEPRECATION" )
window.decorView.systemUiVisibility = (
View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
)
}
}
}
AndroidManifest Configuration
<activity
android:name =".MainActivity"
android:theme ="@style/AppTheme"
android:windowSoftInputMode ="adjustResize"
android:configChanges ="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode" >
</activity >
Capacitor Status Bar Plugin
Installation npm install @capacitor/status-bar
npx cap sync
Usage import { StatusBar , Style } from '@capacitor/status-bar' ;
await StatusBar .setStyle ({ style : Style .Dark });
await StatusBar .setBackgroundColor ({ color : '#ffffff' });
await StatusBar .hide ();
await StatusBar .show ();
await StatusBar .setOverlaysWebView ({ overlay : true });
Common Issues and Solutions
Issue: Content Behind Notch Solution : Add viewport-fit and safe area padding
<meta name ="viewport" content ="viewport-fit=cover" >
body {
padding-top : env (safe-area-inset-top);
}
Issue: Tab Bar Under Home Indicator Solution : Add bottom safe area padding
.tab-bar {
padding-bottom : env (safe-area-inset-bottom);
}
Issue: Landscape Layout Broken Solution : Handle left/right insets
.content {
padding-left : env (safe-area-inset-left);
padding-right : env (safe-area-inset-right);
}
Issue: Keyboard Pushes Content Solution : Use adjustResize and handle insets dynamically
import { Keyboard } from '@capacitor/keyboard' ;
Keyboard .addListener ('keyboardWillShow' , (info ) => {
document .body .style .paddingBottom = `${info.keyboardHeight} px` ;
});
Keyboard .addListener ('keyboardWillHide' , () => {
document .body .style .paddingBottom = 'env(safe-area-inset-bottom)' ;
});
Issue: Safe Areas Not Working in WebView Cause : Missing viewport-fit=cover
<meta
name ="viewport"
content ="width=device-width, initial-scale=1.0, viewport-fit=cover"
/>
Testing Safe Areas
iOS Simulator
Use iPhone with notch (iPhone 14 Pro, etc.)
Test both portrait and landscape
Test with keyboard visible
Android Emulator
Create emulator with camera cutout
Test navigation gesture mode
Test 3-button navigation mode
Preview Different Devices
.debug-safe-areas ::before {
content : '' ;
position : fixed;
top : 0 ;
left : 0 ;
right : 0 ;
height : env (safe-area-inset-top);
background : rgba (255 , 0 , 0 , 0.3 );
z-index : 9999 ;
pointer-events : none;
}
.debug-safe-areas ::after {
content : '' ;
position : fixed;
bottom : 0 ;
left : 0 ;
right : 0 ;
height : env (safe-area-inset-bottom);
background : rgba (0 , 0 , 255 , 0.3 );
z-index : 9999 ;
pointer-events : none;
}
Resources