Skip to content
Layout

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
A plain div
Header
Footer
SafeArea
<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
Compose
<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.

Settings
<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>