Components
SafeArea
A wrapper that pads its children by the device safe-area insets, edge by edge — so notches, the Dynamic Island, the home indicator, and landscape ears never cover your UI.
What safe areas are
Modern phones draw the web page under their hardware: the status bar and notch or Dynamic Island at the top, the home-indicator gesture bar at the bottom, and — in landscape — the camera housing eating into the left or right edge (the “ears”). The safe area is the rectangle guaranteed free of all of that. Anything interactive or legible that sits at a screen edge needs to be padded into it.
The platform standard, nothing invented
Safe-area geometry is the operating system’s to report, and both platforms report it the same way: once your page opts into edge-to-edge rendering with viewport-fit=cover, iOS Safari/WebKit and Android Chrome/WebView expose the notch, Dynamic Island, display cutout and gesture-bar geometry through the CSS env(safe-area-inset-*) variables. That standard is the library’s single source of truth — every padded edge resolves straight to env(safe-area-inset-top) and friends, and the library never invents inset values of its own.
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />. Without viewport-fit=cover the browser letterboxes the page and every inset is 0. On Android 15+ the system renders apps edge-to-edge by default, so handling these insets is no longer optional there.One escape hatch exists for environments where real insets are zero — a desktop browser, an iframe, a test runner: defining --appshell-safe-area-inset-top (and -bottom/-left/-right) overrides the corresponding env() value. The device frames on this site inject 59px/34px that way to simulate an iPhone; your Storybook stories and tests can do the same. It is a simulation hook, not a parallel system.
Shell-wide vs. per-region
Most apps only need <AppShell safeArea> — the shell pads its content column and hands pinned headers their own top inset, so the whole layout stays inside the safe region. Wrap a region in <SafeArea edges={…}> when one specific piece of UI needs its own insets: a full-bleed map or carousel that should respect only the sides, a custom bottom sheet that must clear the home indicator, an overlay that positions itself against raw screen edges.
import { AppShell, Content, Header, SafeArea } from "appshell-react";
export default function App() {
return (
<AppShell safeArea>
<Header behavior="fixed" logo={<span className="font-bold">Atlas</span>} />
<Content className="pb-16">
{/* Full-bleed map: pad only the sides so landscape
notches never cover the controls */}
<SafeArea edges={["left", "right"]} className="h-72 bg-muted">
<div className="flex h-full items-center justify-center text-sm text-muted-foreground">
Map canvas
</div>
</SafeArea>
</Content>
</AppShell>
);
}Props
| Prop | Type | Default | Description |
|---|---|---|---|
| edges | ("top" | "bottom" | "left" | "right")[] | ["top", "bottom", "left", "right"] | Which edges to pad. |
| className | string | — | Extra classes. |
| children* | ReactNode | — | Padded content. |
style prop; use className for everything else.Reading insets in JavaScript
When you need the numbers rather than CSS padding — canvas drawing, absolutely positioned overlays, gesture math — the useSafeArea() hook returns the current insets in pixels, resolved from the same variables. See Hooks.