Safe Area
The padding that keeps an interface out from under the hardware.
Walkthrough
1. Basic
The same screen twice. The tinted strips are the hardware; only the right one is out from under it.
Header
Footer
Header
Footer
<script lang="ts"> import { SafeArea, Text } from 'omaris'; import type { Snippet } from 'svelte'; /* A desktop reports no insets, so the frame below pretends: `--safe-top` and `--safe-bottom` are exactly what the component reads, and a notch is nothing more than those two variables arriving with a value. */ const PHONE = '[--safe-top:44px] [--safe-bottom:34px]';</script>{#snippet bar(label: string)} <div class="rounded-shape-sm bg-primary-container px-3 py-2"> <Text variant="label-md" class="text-primary-container-foreground">{label}</Text> </div>{/snippet}{#snippet phone(caption: string, children: Snippet)} <figure class="flex flex-col items-center gap-2"> <div class="relative h-72 w-52 overflow-hidden rounded-shape-xl border-4 border-foreground/70 bg-surface-container-low {PHONE} max-w-full" > <div class="pointer-events-none absolute inset-x-0 top-0 z-10 h-(--safe-top) bg-destructive/20" ></div> <div class="pointer-events-none absolute inset-x-0 bottom-0 z-10 h-(--safe-bottom) bg-destructive/20" ></div> {@render children()} </div> <figcaption><Text variant="label-sm" tone="muted">{caption}</Text></figcaption> </figure>{/snippet}{#snippet bare()} <div class="flex h-full flex-col justify-between p-2"> {@render bar('Header')} {@render bar('Footer')} </div>{/snippet}{#snippet safe()} <SafeArea class="flex h-full flex-col justify-between p-2"> {@render bar('Header')} {@render bar('Footer')} </SafeArea>{/snippet}<div class="flex flex-wrap justify-center gap-8"> {@render phone('A plain div', bare)} {@render phone('SafeArea', safe)}</div> 2. Edges
A bar reaches the glass; only its contents are held clear. One edge, and a min under it.
Inbox
<script lang="ts"> import { SafeArea, Text } from 'omaris'; const PHONE = '[--safe-top:44px] [--safe-bottom:34px]';</script><div class="flex justify-center"> <div class="relative h-72 w-52 overflow-hidden rounded-shape-xl border-4 border-foreground/70 bg-surface-container-low {PHONE} max-w-full" > <div class="pointer-events-none absolute inset-x-0 top-0 z-10 h-(--safe-top) bg-destructive/20" ></div> <div class="pointer-events-none absolute inset-x-0 bottom-0 z-10 h-(--safe-bottom) bg-destructive/20" ></div> <div class="flex h-full flex-col justify-between"> <!-- The bar's own background runs under the notch, the way an app's does. --> <header class="bg-surface-container-high"> <SafeArea edges={['top']} min={10} class="px-3 pb-2"> <Text variant="label-md">Inbox</Text> </SafeArea> </header> <footer class="bg-surface-container-high"> <SafeArea edges={['bottom']} min={10} class="px-3 pt-2"> <Text variant="label-md">Compose</Text> </SafeArea> </footer> </div> </div></div> 3. Measured
The same insets as numbers, from the safeArea rune. All zero on a desktop is the right answer.
- top
- 0px
- bottom
- 0px
- start
- 0px
- end
- 0px
- keyboard
- 0px
This browser has no env(safe-area-inset-*). Nothing is in the way — a window has no notch, and a page without viewport-fit=cover is never told about one.
<script lang="ts"> import { safeArea, Text } from 'omaris'; const rows = $derived([ ['top', safeArea.top], ['bottom', safeArea.bottom], ['start', safeArea.start], ['end', safeArea.end], ['keyboard', safeArea.keyboard] ] as const);</script><div class="flex flex-col gap-3"> <dl class="grid w-fit grid-cols-2 gap-x-6 gap-y-1 font-mono text-sm tabular-nums"> {#each rows as [name, px] (name)} <dt class="text-muted-foreground">{name}</dt> <dd class="text-end">{px}px</dd> {/each} </dl> <Text variant="body-sm" tone="muted"> {safeArea.supported ? 'This browser understands env(safe-area-inset-*).' : 'This browser has no env(safe-area-inset-*).'} {safeArea.inset ? ' Something is in the way.' : ' Nothing is in the way — a window has no notch, and a page without viewport-fit=cover is never told about one.'} </Text></div> 4. Overridden
Overridden: mode="margin" moves a button off the edge, and the caller's own padding still wins.
<script lang="ts"> import { Button, SafeArea, Text } from 'omaris'; const PHONE = '[--safe-top:44px] [--safe-bottom:34px]';</script><div class="flex justify-center"> <div class="relative h-72 w-52 overflow-hidden rounded-shape-xl border-4 border-foreground/70 bg-surface-container-low {PHONE} max-w-full" > <div class="pointer-events-none absolute inset-x-0 top-0 z-10 h-(--safe-top) bg-destructive/20" ></div> <div class="pointer-events-none absolute inset-x-0 bottom-0 z-10 h-(--safe-bottom) bg-destructive/20" ></div> <!-- `as="section"`, a floor of 2rem, and a `pt-2` the component's own top padding does not fight — the class prop merges last. --> <SafeArea as="section" edges={['top']} min="2rem" class="h-full pt-2"> <Text variant="label-md" class="px-3">Settings</Text> </SafeArea> <div class="absolute inset-x-0 bottom-0 flex justify-end p-3"> <SafeArea edges={['bottom']} mode="margin"> <Button size="sm">Save</Button> </SafeArea> </div> </div></div>