К основному содержанию
скилл CX Codex

safe-area-handling — скилл для Claude Code: вырезы и системные бары

Чинит вёрстку в Capacitor-приложениях: контент под вырезом, Dynamic Island, домашним индикатором и панелью жестов Android.

Установить в Claude Code · Через Skills CLI (Vercel) Все способы и платформы ↓
npx skills add Cap-go/capgo-skills --skill safe-area-handling -a claude-code
Открыть на GitHub

Что делает safe-area-handling

Скилл разбирает, почему шапка уезжает под статус-бар, а таббар прячется за панелью жестов, и что с этим делать. Внутри — разбор поведения инсетов по платформам и версиям: на iOS WebView на весь экран, и env(safe-area-inset-*) работает только с viewport-fit=cover; на Android в Capacitor 8+ конфиг android.adjustMarginsForEdgeToEdge удалён, а инсетами занимается core-плагин SystemBars.

Отдельный блок — Android 15 и 16. При targetSdk 36 edge-to-edge включается принудительно, и overlaysWebView с backgroundColor из @capacitor/status-bar перестают действовать. На Android WebView ниже 140 env() отдаёт ноль из-за бага Chromium, поэтому SystemBars подставляет переменные --safe-area-inset-* при insetsHandling: 'css'. Скилл советует строить фолбэк-цепочку один раз на уровне оболочки макета, а не в каждом компоненте.

Есть и список грабель. Если поставить ios.contentInset: 'always' вместе с CSS-отступами, инсеты применятся дважды. Захардкоженные 20/44/47/59 px не годятся: высоты зависят от устройства, ориентации и размера окна. Инсеты меняются на ходу при повороте, в оконном режиме iPad и в split screen, поэтому кэшировать их при старте нельзя. Для Ionic и Konsta скилл не нужен — они считают отступы сами.

Какие задачи решает

  • убрать контент из-под статус-бара и Dynamic Island
  • вернуть таббар из-под панели жестов Android
  • починить вёрстку после апгрейда до Capacitor 8 или targetSdk 35+
  • получить ненулевые инсеты на Android WebView старее 140
  • поправить боковые отступы в ландшафте и оконном режиме iPad

Примеры запросов агенту

  • Шапка уезжает под статус-бар на iPhone с Dynamic Island — почини через safe-area-handling
  • После перехода на targetSdk 36 сломался layout в Android-приложении, разберись
  • env(safe-area-inset-bottom) возвращает 0 на Android — что делать?

Как установить safe-area-handling

1Через Skills CLI (Vercel)

npx skills add Cap-go/capgo-skills --skill safe-area-handling -a claude-code

2Вручную: положить файлы

~/.claude/skills/safe-area-handling/SKILL.md

Папка скилла целиком; для одного проекта — .claude/skills/safe-area-handling/

По шагам, с командами для каждой платформы: Как установить скилл в Claude Code: пять способов

Вопросы о safe-area-handling

Как установить safe-area-handling в Claude?

Выполните npx skills add Cap-go/capgo-skills --skill safe-area-handling -a claude-code. Скилл окажется в ~/.claude/skills/safe-area-handling/SKILL.md.

Что делает safe-area-handling?

Помогает разобраться с вырезами, Dynamic Island, домашним индикатором и системными барами в Capacitor-приложениях: от viewport-fit=cover до плагина SystemBars и принудительного edge-to-edge на Android 15/16.

Чем этот скилл отличается от capacitor-keyboard?

safe-area-handling про системные бары и вырезы. Перекрытие клавиатурой — не его тема, для этого есть capacitor-keyboard; на iOS нижний инсет остаётся и при открытой клавиатуре, там нужен max() с её высотой.

Исходный текст

SKILL.md · MIT
---
name: safe-area-handling
description: 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).
---

# 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-*)` is `0px` on Android, or correct only after rotation
- Layout broke after Capacitor 8 upgrade or raising `targetSdkVersion` to 35/36
- `StatusBar.setOverlaysWebView` / `setBackgroundColor` stopped 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-toolbar` theming: `ionic-design`; Konsta `safeAreas`: `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

1. **Inspect**: `index.html` viewport meta, `capacitor.config.*` (`ios.contentInset`, `android.adjustMarginsForEdgeToEdge`, `plugins.SystemBars`, `plugins.StatusBar`), `@capacitor/core` + `@capacitor/status-bar` versions, `android/variables.gradle` `targetSdkVersion`, global CSS that sets `padding-top`, UI framework (Ionic / Konsta handle insets themselves).
2. **Decide the design**: full-bleed (draw under bars, pad content) vs. contained (content between bars). Ask the user if mockups are unclear.
3. **Full-bleed (recommended)**: `viewport-fit=cover` + the CSS fallback chain below, applied once at the layout shell, not per component.
4. **Remove legacy config**: delete `android.adjustMarginsForEdgeToEdge`; drop Android uses of `StatusBar.setOverlaysWebView` / `setBackgroundColor` (keep for iOS if wanted); prefer `SystemBars.setStyle` for icon color.
5. **Framework check**: Ionic already applies `--ion-safe-area-*`; Konsta uses `safeAreas`. Do not double-pad.
6. **Verify** on devices (below).

Only load a reference when its topic is in play:
- [references/android-edge-to-edge.md](references/android-edge-to-edge.md) - SystemBars config, status-bar migration, nav bar color, WebView version checks
- [references/css-patterns.md](references/css-patterns.md) - layout shell, tab bars, reading insets in JS, debug overlay, resizable windows

## Baseline

```html
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
```

```css
: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.

```ts
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=cover` is the #1 cause of `env()` = 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 (see `capacitor-keyboard`).
- `100vh` includes the area under system bars; use `100dvh` or flex layout with a fixed shell.
- Ionic: `ion-content` with `fullscreen` + `ion-header` already pads; adding body padding shifts everything twice.

## Verification

1. `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.
2. Check Android WebView version: `adb shell dumpsys package com.google.android.webview | grep versionName` (< 140 relies on injected variables).
3. In Safari Web Inspector / `chrome://inspect`, run:

```js
getComputedStyle(document.documentElement).getPropertyValue('--sat');
document.querySelector('meta[name=viewport]').content.includes('viewport-fit=cover');
```

4. Confirm no element overlaps bars; tab bar taps work above the gesture bar; no double padding.
5. Grep for leftovers:

```bash
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/

© Cap-go · MIT · Cap-go/capgo-skills

Похожие скиллы

Всё по теме «Мобильная разработка» →
maui-safe-area dotnet Помогает настроить безопасные зоны и отображение edge-to-edge в приложениях на .NET 10+. Пригодится, если контент перекрывает системные панели или экранную клавиатуру. CX Codex +1 3 200установок capacitor-expert capawesome-team Скилл-справочник по Capacitor: архитектура, CLI, плагины, интеграция с фреймворками и Capawesome Cloud. Пригодится, когда агент собирает или чинит кросс-платформенное приложение. CX Codex +1 1 100установок capacitor-app-creation capawesome-team Скилл ведёт агента по шагам при создании Capacitor-приложения с нуля: каркас веб-проекта, настройка, нативные платформы, запуск. CX Codex +1 820установок mobile-touch dylantarre Скилл для ИИ-агента, который помогает проектировать жесты, хаптику и анимации в iOS и Android. Пригодится, когда нужно оживить интерфейс без выдумывания таймингов с нуля. CX Codex +1 2 400установок imagegen-frontend-mobile nexu-io Генерирует premium mobile-экраны и флоу для iOS, Android и кросс-платформы с чистой иерархией, читаемым текстом и контролируемой палитрой. CX Codex +1 1 700установок ui-mobile alinaqi Задаёт агенту правила мобильного интерфейса: минимальный размер нажатия 44×44, контраст по WCAG 2.1 AA, видимые границы кнопок. CX Codex +1 1 200установок

Из того же набора29

capacitor-plugins Помогает выбрать плагин Capacitor под нативную функцию, поставить его и проверить, что он реально слинкован в iOS и Android. CX Codex +1 1 600установок ionic-design Проверяет и создаёт интерфейсы на Ionic Framework для приложений на Capacitor. Помогает настраивать темы, режимы iOS и MD, а также обновлять компоненты с Ionic 8 до Ionic 9. CX Codex +1 1 600установок ios-android-logs Скилл показывает, откуда брать логи и краш-отчёты Capacitor-приложения на iOS и Android, и как отфильтровать их до своего процесса. CX Codex +1 1 300установок capacitor-testing Скилл разбирает, каким слоем тестировать Capacitor-приложение: Vitest с моками плагинов, Playwright, Appium и нативные тесты. CX Codex +1 1 300установок capacitor-app-store Ведёт сборку Capacitor от «работает на моём устройстве» до публикации в App Store и Google Play: версии, подпись, загрузка IPA/AAB, TestFlight и треки Play. CX Codex +1 1 200установок capacitor-accessibility Скилл для ИИ-агента, который разбирает доступность Capacitor-приложений: VoiceOver, TalkBack, Dynamic Type, Switch Control и масштаб шрифта Android. CX Codex +1 1 200установок capacitor-security Проверяет приложение на Capacitor на утечки секретов, слабую защиту хранилища и транспорта, а потом помогает всё это починить. CX Codex +1 1 200установок capacitor-push-notifications Скилл для настройки и отладки удалённых пушей в Capacitor: FCM на Android, APNs на iOS. Пригодится, когда registration не срабатывает или уведомления не показываются. CX Codex +1 1 100установок

Спонсоры проекта

Компании, которые помогают держать аналитику открытой