# Tailwind Safe Area: Bottom Bars, FABs und Toasts

> Tailwind Safe Area in v4: viewport-fit=cover aktivieren, Leisten Padding aus env(safe-area-inset-bottom) geben, FABs und Toasts vom Home-Indikator fernhalten.

- Autor: [Serban Rusu](https://wingo-ui.com/blog/authors/serban), Founder of Wingo UI
- Veröffentlicht: 9. Okt. 2026
- Kategorie: [Mobile UI mit React](https://wingo-ui.com/de/blog/category/mobile-ui)
- Lesezeit: 11 Min.
- Kanonische URL: https://wingo-ui.com/de/blog/tailwind-safe-area-insets
- Englische Fassung: https://wingo-ui.com/blog/tailwind-safe-area-insets

## Kurz gesagt

Tailwind CSS v4 hat keine Safe-Area-Utilities. Du setzt also viewport-fit=cover und gibst jedem Element, das an einem Bildschirmrand klebt, Padding aus env(safe-area-inset-*), entweder als Arbitrary Value wie pb-[max(env(safe-area-inset-bottom),12px)] oder als ein paar @utility-Klassen. Untere Leisten bleiben auf bottom-0 und bekommen das Padding innen, damit ihr Hintergrund unter dem Home-Indikator weiterläuft. Ein schwebender Button sitzt beim größeren Wert aus Leistenhöhe und Inset plus einem Abstand. Eine zweite Leiste bekommt nur für den Teil des Insets Padding, den die erste nicht abdeckt, damit sich der Abstand nie verdoppelt.

Du lässt die Seite mit `viewport-fit=cover` den ganzen Screen füllen, fixierst eine Tab-Leiste auf `bottom-0`, öffnest alles auf dem iPhone, und der Home-Indikator liegt auf deinen Labels. Drehst du das Smartphone quer, schneidet die Notch den Anfang jeder Zeile ab. In Tailwind CSS v4 ist die Tailwind Safe Area deine Sache, denn der Core bringt dafür keine Klassen mit: Alles, was an einem Rand klebt, muss sich sein Padding selbst aus `env(safe-area-inset-*)` holen. Dieses Tutorial baut den unteren Teil eines Smartphone-Screens mit Tab-Leiste, Kaufleiste, schwebendem Action-Button und Toasts. Keines davon verschwindet unter der Notch oder dem Home-Indikator, und keines verdoppelt den Abstand, wenn sie übereinanderliegen.

Safe Areas sind eine der neun Regeln im Guide zu [Mobile-first React-Komponenten](https://wingo-ui.com/de/blog/mobile-first-react-components). Dieser Beitrag ist die Schritt-für-Schritt-Version, mit der Rechnung für übereinanderliegende Leisten.

## Was ändert viewport-fit cover?

`viewport-fit=cover` sagt dem Browser, dass sich deine Seite selbst um die Aussparungen des Displays kümmert. Mit dem Standard `viewport-fit=auto` rückt Safari auf dem iPhone die Seite ein, damit nichts unter der Notch oder den abgerundeten Ecken landet, und füllt die übrigen Streifen mit der Hintergrundfarbe von `<html>` oder `<body>` ([WebKit, 2017](https://webkit.org/blog/7929/designing-websites-for-iphone-x/)). Mit `cover` füllt der Viewport das ganze Display, und die [Viewport-Referenz von MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/meta/name/viewport) empfiehlt ausdrücklich, dann die Variablen für die Safe Area Insets zu nutzen, damit wichtige Inhalte sichtbar bleiben.

Android kam 2025 dazu. Ab [Chrome 135](https://developer.chrome.com/docs/css-ui/edge-to-edge) zeichnet Chrome auf Android-Smartphones Edge to Edge. Standardmäßig zeigt es dabei einen „Chin“ über der Gestenleiste, der beim Scrollen wegrutscht, und eine Leiste mit `position: fixed; bottom: 0` landet hinter der Navigationsleiste, sobald der Chin weg ist. Eine Seite mit `viewport-fit=cover` geht schon beim Laden bis an den Rand und zeigt den Chin nie. So oder so zählen die Insets jetzt auch auf Android.

Im Next.js App Router kommt das Meta-Tag aus einem `viewport`-Export im Root-Layout, und das muss eine Server Component sein:

```tsx
// app/layout.tsx
import type { Viewport } from "next";

export const viewport: Viewport = {
  width: "device-width",
  initialScale: 1,
  viewportFit: "cover",
};
```

Next.js rendert daraus `<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">`. Ohne Next.js schreibst du das Tag selbst. Versteh `cover` als Versprechen: Ab jetzt sorgt jedes Element, das du an einen Rand heftest, selbst für sein Padding.

## Warum liefert env safe-area-inset-bottom 0?

Meist fehlt der Seite `viewport-fit=cover`, oder auf deinem Testgerät verdeckt nichts diesen Rand. Ohne `cover` hält Safari die Seite selbst innerhalb der Safe Area, also bleibt auf dem iPhone kein Inset zu melden. Desktop-Browser melden ebenfalls 0 ([MDN zu env()](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Values/env)). Ein Layout, das in einem schmalen Desktop-Fenster gut aussieht, beweist also nichts. Ein iPhone mit Home-Button wie das iPhone SE hat keinen Home-Indikator und meldet unten 0. Deshalb kombiniert jede Regel unten das Inset mit einem Mindestwert, statt sich allein darauf zu verlassen.

Prüf die gerenderte Seite, nicht deinen Quellcode: Öffne den Head im Inspector und stell sicher, dass es genau ein Viewport-Tag gibt und dass es `viewport-fit=cover` enthält. Ein zweites Tag, das ein Plugin oder ein handgeschriebener `<head>` einfügt, kann das erste überschreiben. Außerdem unterscheiden die Variablennamen Groß- und Kleinschreibung, `env(SAFE-AREA-INSET-BOTTOM)` fällt also stillschweigend auf den Fallback zurück.

## Wie schreibst du eine Utility für Tailwind Safe Area Inset Bottom?

Nimm für einen Einzelfall einen Arbitrary Value und definier ein paar `@utility`-Klassen, sobald er an mehr als zwei Stellen auftaucht. Tailwind CSS v4 hat im Core keine Safe-Area-Utilities: Stand Version 4.3.3 sind die einzigen `-safe`-Klassen solche für die Ausrichtung wie `justify-center-safe`. Sie bilden das CSS-Schlüsselwort `safe` für Overflow ab und haben mit der Notch nichts zu tun.

Der Arbitrary Value funktioniert überall: `pb-[max(env(safe-area-inset-bottom),12px)]`. Für ein Projekt legst du ein kleines Set in dein Stylesheet:

```css
/* app/globals.css */
@import "tailwindcss";

@utility pt-safe {
  padding-top: env(safe-area-inset-top, 0px);
}

@utility pb-safe {
  padding-bottom: env(safe-area-inset-bottom, 0px);
}

/* das Inset, aber nie weniger als eine Spacing-Stufe: pb-safe-or-3 */
@utility pb-safe-or-* {
  padding-bottom: max(env(safe-area-inset-bottom, 0px), --spacing(--value(integer)));
}

/* Querformat: die Seite mit der Notch bekommt das Inset, die andere behält den Rand: px-safe-or-4 */
@utility px-safe-or-* {
  padding-left: max(env(safe-area-inset-left, 0px), --spacing(--value(integer)));
  padding-right: max(env(safe-area-inset-right, 0px), --spacing(--value(integer)));
}

/* ein Abstand zusätzlich zum Inset, für schwebende Elemente: bottom-safe-offset-4 */
@utility bottom-safe-offset-* {
  bottom: calc(env(safe-area-inset-bottom, 0px) + --spacing(--value(integer)));
}
```

Das `-*` am Ende eines Namens macht eine Utility funktional, und `--value(integer)` liest die Zahl dahinter. `pb-safe-or-3` wird also zu `padding-bottom: max(env(safe-area-inset-bottom, 0px), calc(var(--spacing) * 3))`, und Varianten wie `md:pb-safe` funktionieren wie bei jeder anderen Klasse. Willst du lieber das komplette Set installieren (Margins, Scroll Padding, `h-dvh-safe` sowie `-offset-`- und `-or-`-Formen für alles), ergänzt das Paket [tailwindcss-safe-area](https://www.npmjs.com/package/tailwindcss-safe-area) es mit `@import "tailwindcss-safe-area";` (Version 1.3.0, MIT-Lizenz, Stand Oktober 2026).

Die beiden Funktionen haben unterschiedliche Aufgaben. Nimm `max()`, wenn das Inset deinen normalen Abstand ersetzt, etwa beim unteren Padding einer Leiste oder beim Rand eines Toasts: Auf einem Smartphone mit Home-Indikator gewinnt das Inset, auf dem Laptop deine 12px. Nimm `calc()`, wenn du zusätzlich zum Inset einen Abstand brauchst, etwa für einen Button, der 16px über dem Indikator schwebt. Der ursprüngliche Beitrag von WebKit nutzt die Form mit `max()` aus demselben Grund für das seitliche Padding.

## Wie bekommt eine fixierte untere Leiste das richtige Padding?

Gib der Leiste innen Padding und lass sie selbst auf `bottom-0`. Ihr Hintergrund läuft dann unter den Home-Indikator, und das Padding hebt die tippbare Zeile darüber. Der ganze Fix für iPhone Home Indicator CSS ist eine einzige Klasse auf der Leiste, `pb-safe`:

```tsx
// components/tab-bar.tsx
import Link from "next/link";
import { House, Search, ShoppingBag, UserRound } from "lucide-react";

const tabs = [
  { href: "/", label: "Start", icon: House },
  { href: "/search", label: "Suche", icon: Search },
  { href: "/cart", label: "Warenkorb", icon: ShoppingBag },
  { href: "/account", label: "Konto", icon: UserRound },
];

export function TabBar() {
  return (
    <nav
      aria-label="Hauptnavigation"
      className="fixed inset-x-0 bottom-0 z-40 border-t border-border bg-background/90 pb-safe px-safe-or-2 backdrop-blur md:hidden"
    >
      <ul className="flex h-16">
        {tabs.map(({ href, label, icon: Icon }) => (
          <li key={href} className="flex-1">
            <Link
              href={href}
              className="flex h-full flex-col items-center justify-center gap-1 text-[11px] font-medium touch-manipulation"
            >
              <Icon aria-hidden className="size-6" />
              {label}
            </Link>
          </li>
        ))}
      </ul>
    </nav>
  );
}
```

Die Zeile bleibt überall 64px hoch. Auf einem Smartphone mit Home-Indikator wächst die Leiste um das Inset; auf dem Desktop ist das Inset 0 und nichts ändert sich. `px-safe-or-2` hält den ersten und den letzten Tab im Querformat von der Notch fern. Jeder Tab ist ein Viertel des Screens breit und 64px hoch, also deutlich über der [minimalen Touch-Target-Größe](https://wingo-ui.com/de/blog/minimum-touch-target-size-react-tailwind).

Die Leiste mit `bottom-[env(safe-area-inset-bottom)]` nach oben zu schieben, sieht auf einem Screenshot ähnlich aus. Die Seite scrollt dann aber durch einen sichtbaren Streifen unter der Leiste. Chromes Guide zeigt denselben Effekt auf Android.

Der Inhalt hinter der Leiste braucht denselben Platz, sonst scrollt die letzte Zeile einer Liste nie ins Bild:

```tsx
<main className="pb-[calc(4rem+env(safe-area-inset-bottom))] md:pb-0">{children}</main>
```

### Was ist mit Chromes Warnung vor Padding mit dem Inset?

Die Warnung gilt für Seiten, die den Chin behalten. Chromes Guide rät davon ab, `padding-bottom` auf `env(safe-area-inset-bottom)` zu setzen: Ohne `cover` ändert sich das Inset, während der Chin wegrutscht, das Padding erzwingt bei jedem Schritt ein neues Layout, und Chrome hört auf, den Chin wegzuschieben, sobald es dieses Muster erkennt. Mit `viewport-fit=cover` gibt es keinen Chin, der wegrutschen könnte.

Kannst du `cover` nicht setzen, nimm Chromes Alternative: Reservier vorab das größte Inset und verschieb die Leiste mit `bottom`, über ein Muster `calc(env(safe-area-inset-bottom, ...) - ...)`, für das Chrome einen Fast Path hat. Tailwinds Preflight setzt `box-sizing: border-box`, also muss die Höhe das Padding mit einschließen:

```tsx
<nav className="fixed inset-x-0 [--safe-max-b:env(safe-area-max-inset-bottom,36px)] h-[calc(4rem+var(--safe-max-b))] pb-(--safe-max-b) bottom-[calc(env(safe-area-inset-bottom,0px)-var(--safe-max-b))]">
  {/* Tabs */}
</nav>
```

`safe-area-max-inset-bottom` kam mit Chrome 135. Der Fallback von 36px ist der Wert, den Chromes Guide für Browser ohne diese Variable nutzt.

## Wie stapelst du einen FAB über einer Tab-Leiste, ohne den Abstand zu verdoppeln?

Leg die volle Höhe der Leiste inklusive Inset in eine CSS-Variable und positionier den Button beim größeren Wert aus Variable und Inset, plus deinem Abstand. Wer beides addiert, zählt das Inset doppelt: Die Höhe der Leiste enthält das Inset schon, also schwebt der Button bei `bar + inset + 16px` um einen ganzen Home-Indikator-Streifen zu hoch.

Für die statische Leiste von oben reicht für die Variable reines CSS. Setz sie auf einen Wrapper, damit der Button sie erbt, und setz sie dort zurück, wo die Leiste versteckt ist:

```tsx
// components/mobile-shell.tsx
import type { ReactNode } from "react";
import { Plus } from "lucide-react";
import { TabBar } from "@/components/tab-bar";

export function MobileShell({ children }: { children: ReactNode }) {
  return (
    <div className="[--bottom-bar-offset:calc(4rem+env(safe-area-inset-bottom,0px))] md:[--bottom-bar-offset:0px]">
      <main className="pb-[calc(var(--bottom-bar-offset)+1rem)]">{children}</main>
      <button
        type="button"
        aria-label="Neues Inserat"
        className="fixed right-[calc(1rem+env(safe-area-inset-right,0px))] bottom-[calc(max(var(--bottom-bar-offset),env(safe-area-inset-bottom,0px))+1rem)] z-40 grid size-14 place-items-center rounded-full bg-foreground text-background shadow-lg touch-manipulation"
      >
        <Plus aria-hidden className="size-6" />
      </button>
      <TabBar />
    </div>
  );
}
```

Ab `md` ist die Variable `0px`, also fällt `max()` auf das Inset zurück, und der Button schwebt 16px über der Unterkante, bei Geräten mit Inset 16px über dem Inset. Derselbe Wrapper gibt `main` sein unteres Padding, die Höhe der Leiste steht also nur an einer Stelle.

Ist die Höhe vorher nicht bekannt (eine Leiste mit anderer Größe pro Seite, eine Kaufleiste, die nach dem Scrollen hereingleitet), miss sie stattdessen: Ein `ResizeObserver` auf der Leiste schreibt ihre Höhe in die Variable auf `<html>`. Die [Bottom Tab Bar](https://wingo-ui.com/components/bottom-tab-bar) von Wingo UI schreibt `--bottom-bar-offset` auf diese Weise, wenn sie fixiert ist, und der Wert fällt auf 0, solange `md:hidden` die Leiste versteckt. Die [Sticky CTA Bar](https://wingo-ui.com/components/sticky-cta-bar) schreibt `--cta-bar-offset`, ihre sichtbare Höhe, die auf 0 fällt, solange sie versteckt ist. Der [FAB](https://wingo-ui.com/components/fab) liest beide:

```css
bottom: calc(max(var(--bottom-bar-offset, 0px) + var(--cta-bar-offset, 0px), env(safe-area-inset-bottom, 0px)) + 16px);
```

Die Komponenten brauchen also keine Props, um zusammenzuspielen. Render sie nebeneinander, und der Button bleibt über den Leisten, die gerade zu sehen sind:

```tsx
"use client";

import { FileText, House, Inbox, UserPlus, UserRound, Users } from "lucide-react";
import { BottomTabBar, type BottomTabBarItem } from "@/components/ui/bottom-tab-bar";
import { Fab, type FabAction } from "@/components/ui/fab";

const tabs: BottomTabBarItem[] = [
  { label: "Start", href: "/", icon: <House /> },
  { label: "Kunden", href: "/clients", icon: <Users /> },
  { label: "Posteingang", href: "/inbox", icon: <Inbox />, badge: 4 },
  { label: "Profil", href: "/profile", icon: <UserRound /> },
];

const actions: FabAction[] = [
  { id: "client", label: "Neuer Kunde", icon: <UserPlus />, href: "/clients/new" },
  { id: "invoice", label: "Neue Rechnung", icon: <FileText />, href: "/invoices/new" },
];

export function MobileChrome() {
  return (
    <>
      <Fab label="Hinzufügen" actions={actions} hideOnScroll />
      <BottomTabBar items={tabs} className="md:hidden" hideOnScroll />
    </>
  );
}
```

Ein Haken in Next.js: Ohne `LinkProvider` rendern die Tabs einfache `<a>`-Elemente. Jedes Antippen lädt dann die Seite neu, und die Leiste kennt ihren aktiven Tab nur aus ihrem eigenen State. Umschließ die App einmal mit `<LinkProvider component={Link} currentPath={usePathname()}>` aus `@/lib/navigation` (in einer Client Component), dann navigieren die Tabs über `next/link` und heben die aktuelle Route hervor.

> Live-Demo (FAB): Tipp auf „Add“, um das Speed Dial zu öffnen, und scroll dann durch die Kundenliste: Beim Runterscrollen klappt das Label ins Icon, beim Hochscrollen kommt es zurück. Probier sie unter [FAB](https://wingo-ui.com/components/fab) aus und installier sie mit `npx wingo-ui@latest add fab`.

Die Demo hat keine Tab-Leiste, und auf dem Desktop ist das Inset 0, also sitzt der Button 16px über der Unterkante des Rahmens. Wir haben in Chromium mit dem Test am Ende dieses Beitrags ein unteres Inset von 34px emuliert: Die Regel des FAB wurde zu `bottom: 50px` (34 + 16) berechnet, das untere Padding der Bottom Tab Bar zu 34px.

## Wie setzt du eine Kaufleiste auf eine Tab-Leiste?

Positionier die Kaufleiste mit `bottom: var(--bottom-bar-offset)` und gib ihr nur für den Teil des Insets Padding, den die Tab-Leiste nicht schon abdeckt. Die Sticky CTA Bar nutzt für ihr unteres Padding diese Klasse:

```text
pb-[max(0px,calc(env(safe-area-inset-bottom,0px)-var(--bottom-bar-offset,0px)))]
```

Liegt eine Tab-Leiste darunter, wird die Subtraktion negativ, `max()` klemmt sie auf 0, und die Kaufleiste fügt keinen eigenen Abstand hinzu. Auf einer Produktseite ohne Tab-Leiste ist die Variable 0, und die Kaufleiste übernimmt das ganze Inset selbst. Ihre Seiten nutzen `pl-[max(1rem,env(safe-area-inset-left,0px))]` und die passende Klasse für rechts, damit der Preis auf einem Smartphone im Querformat frei von der Notch bleibt.

```tsx
// components/buy-bar.tsx
import { StickyCtaBar } from "@/components/ui/sticky-cta-bar";

export function BuyBar({ onAddToCart }: { onAddToCart: () => Promise<void> }) {
  return (
    <StickyCtaBar
      price={349}
      compareAtPrice={449}
      locale="en-US"
      actionLabel="In den Warenkorb"
      tone="brand"
      hideFrom="md"
      onAction={onAddToCart}
    />
  );
}
```

Render sie neben der `BottomTabBar` aus dem vorigen Abschnitt, und sie sitzt auf der Tab-Leiste; render sie allein, und sie sitzt an der Unterkante. Solange das Promise aus `onAction` aussteht, zeigt der Button einen Spinner.

> Live-Demo (Sticky CTA Bar): Scroll durch das Inserat: Die Leiste bleibt unten fixiert, links der Makler, rechts „Call“. Drückst du „Call“, zeigt der Button einen Spinner, ohne seine Breite zu ändern. Probier sie unter [Sticky CTA Bar](https://wingo-ui.com/components/sticky-cta-bar) aus und installier sie mit `npx wingo-ui@latest add sticky-cta-bar`.

Die Demo fixiert die Leiste mit `position="sticky"` in einem Smartphone-Rahmen, denn `fixed` würde sie an dein Browserfenster heften. Das Safe-Area-Padding ist in beiden Modi gleich, und `hideFrom="md"` versteckt die Leiste auf breiten Screens per CSS, wo die Seite ihren eigenen Kaufen-Button zeigt.

## Wohin gehören Toasts auf einem Smartphone mit Home-Indikator?

Setz den Toast-Stapel auf den größeren Wert aus Inset und Rand. Hat die App eine untere Tab-Leiste, zeig Toasts auf Smartphones oben an. Den ersten Teil erledigt der [Toast](https://wingo-ui.com/components/toast) von Wingo UI für dich. Unter 768px gehen Toasts über die volle Breite mit 16px Rand (`mobileOffset`), und jede Kante nutzt `max(env(safe-area-inset-*), 16px)`. Ein Stapel unten bleibt so frei vom Home-Indikator, ein Stapel oben frei von Notch und Statusleiste. Unser [Vergleich der Sonner-Alternativen](https://wingo-ui.com/de/blog/sonner-alternative-react-toast) zeigt, welche anderen Toast-Bibliotheken das Safe-Area-Padding für dich übernehmen.

Der Toaster liest `--bottom-bar-offset` nicht, ein Stapel unten würde auf dem Smartphone also eine Tab-Leiste verdecken. Hat deine App eine, schieb die Toasts auf Smartphones nach oben. Mit dem Viewport-Export aus dem ersten Abschnitt sieht das Root-Layout am Ende so aus:

```tsx
// app/layout.tsx
import type { ReactNode } from "react";
import type { Viewport } from "next";
import { Toaster } from "@/components/ui/toast";
import "./globals.css";

export const viewport: Viewport = {
  width: "device-width",
  initialScale: 1,
  viewportFit: "cover",
};

export default function RootLayout({ children }: { children: ReactNode }) {
  return (
    <html lang="en">
      <body>
        {children}
        <Toaster mobilePosition="top" />
      </body>
    </html>
  );
}
```

> Live-Demo (Toast): Drück „Save“, dann „Failed payment“: Auf einem breiten Screen stapeln sich die Toasts unten rechts, unter 768px gehen sie oben über die volle Breite. Wisch einen weg, um ihn zu schließen. Probier sie unter [Toast](https://wingo-ui.com/components/toast) aus und installier sie mit `npx wingo-ui@latest add toast`.

Die Platzierung auf dem Desktop behält ihre eigenen Props `position` und `offset`, der Stapel in der Ecke bleibt auf dem Laptop also, wo er war.

## Wie testest du Safe Areas ohne iPhone?

Auf einem Mac führt der iOS Simulator von Xcode Safari mit den echten Insets jedes simulierten Geräts aus. In der CI kann Chromium sie vortäuschen: Das Chrome DevTools Protocol hat den experimentellen Befehl `Emulation.setSafeAreaInsetsOverride`, der die Werte von `env(safe-area-inset-*)` und `env(safe-area-max-inset-*)` überschreibt, und Playwright kann ihn über eine CDP-Session senden:

```ts
// e2e/safe-area.spec.ts
import { test, expect } from "@playwright/test";

test.use({ viewport: { width: 390, height: 844 } });

test("die Tab-Leiste hält Abstand zum Home-Indikator", async ({ page, context, browserName }) => {
  test.skip(browserName !== "chromium", "der Override ist ein Befehl des Chrome DevTools Protocol");
  const cdp = await context.newCDPSession(page);
  await cdp.send("Emulation.setSafeAreaInsetsOverride", {
    insets: { top: 47, bottom: 34, left: 0, right: 0 },
  });
  await page.goto("/"); // baseURL kommt aus playwright.config.ts
  const nav = page.getByRole("navigation", { name: "Hauptnavigation" });
  await expect(nav).toHaveCSS("padding-bottom", "34px");
});
```

Wir haben dieses Muster im Oktober 2026 mit Playwright 1.63 und dem mitgelieferten Chromium gegen die Previews von Bottom Tab Bar, FAB und Sticky CTA Bar laufen lassen. Nimm die Inset-Werte der Geräte, die dir wichtig sind, und ergänz einen zweiten Fall mit `bottom: 0`. So fallen Leisten auf, die auf Smartphones ohne Home-Indikator ihr Mindest-Padding verlieren. Der Befehl ist als experimentell markiert, also leg deine Playwright-Version fest.

## Wo fängst du an?

Ergänz `viewportFit: "cover"` und die fünf Utilities von oben und reparier dann die Leisten von unten nach oben: zuerst die Tab-Leiste, dann alles, was darüber schwebt. Safe Areas betreffen aber mehr als Leisten: In Wingo UI 1.7.0 lesen 38 Komponentendateien `env(safe-area-inset-*)`, von Sheets und Dialogen bis zum Cookie-Consent-Banner und zur Image Lightbox. Der [Guide zum Bottom Sheet](https://wingo-ui.com/de/blog/react-bottom-sheet-for-the-web) zeigt, wie der Footer eines Sheets mit dem Home-Indikator umgeht.

Willst du die Toast-Platzierung aus diesem Beitrag, ohne sie selbst zu schreiben: Der Toast ist kostenlos. Führ `npx wingo-ui@latest add toast` aus und bind den Toaster einmal in dein Root-Layout ein. Bottom Tab Bar, Sticky CTA Bar und FAB gehören zu [Wingo UI Pro](https://wingo-ui.com/de/pricing). Weitere Guides wie diesen findest du unter [Mobile UI in React](https://wingo-ui.com/de/blog/category/mobile-ui).

## Komponenten in diesem Artikel

- [Sticky CTA Bar](https://wingo-ui.com/components/sticky-cta-bar) (Pro): A React sticky CTA bar for product, listing and checkout pages on phones: the price on one side, the main action on the other. Installation: `npx wingo-ui@latest add sticky-cta-bar`
- [Bottom Tab Bar](https://wingo-ui.com/components/bottom-tab-bar) (Pro): A React bottom tab bar for phones: 3 to 5 tabs at the thumb with an animated pill, badges, a raised center action and hide on scroll. Installation: `npx wingo-ui@latest add bottom-tab-bar`
- [FAB](https://wingo-ui.com/components/fab) (Pro): A React floating action button (FAB) for a phone screen's main action, with a labeled or radial speed dial and a label that folds on scroll. Installation: `npx wingo-ui@latest add fab`
- [Toast](https://wingo-ui.com/components/toast) (Free): React toast notifications: mount one Toaster and call toast() from anywhere, with stacking, swipe to dismiss and a pause while you read. Installation: `npx wingo-ui@latest add toast`

## Häufige Fragen

### Hat Tailwind CSS Safe-Area-Utilities?

Nicht im Core. Stand Tailwind CSS 4.3.3 (Oktober 2026) gibt es keine Klasse pb-safe. Du nutzt also Arbitrary Values wie pb-[max(env(safe-area-inset-bottom),12px)], definierst ein paar Klassen mit @utility oder installierst das Paket tailwindcss-safe-area.

### Warum ist env(safe-area-inset-bottom) immer 0?

Auf dem iPhone fehlt meist viewport-fit=cover. Dann hält Safari die Seite selbst innerhalb der Safe Area und hat kein Inset zu melden. Desktop-Browser und iPhones mit Home-Button melden unten ebenfalls 0, also kombinierst du das Inset über max() mit einem Mindestwert.

### Brauche ich viewport-fit=cover auf Android?

Nicht für Edge to Edge: Seit Chrome 135 zeichnet Chrome auf Android-Smartphones ohnehin bis an den Rand, mit einem Chin über der Gestenleiste, der beim Scrollen wegrutscht. Mit viewport-fit=cover reicht die Seite von Anfang an bis zur Unterkante und der Chin erscheint nie, also brauchen fixierte untere Leisten das Safe-Area-Inset auf Android genauso wie auf dem iPhone.

### Sollte eine untere Leiste Padding nutzen oder bottom: env(safe-area-inset-bottom)?

Padding, auf einer Seite mit viewport-fit=cover. Die Leiste bleibt an der Unterkante und ihr Hintergrund läuft unter den Home-Indikator, während bottom: env() einen Streifen lässt, durch den die Seite scheint. Seiten, die den Chin von Chrome behalten, nutzen stattdessen das Muster von Chrome mit safe-area-max-inset-bottom.

### Wie teste ich Safe Area Insets ohne iPhone?

Nutz den iOS Simulator von Xcode auf einem Mac oder emuliere die Insets in Chromium mit dem experimentellen DevTools-Protocol-Befehl Emulation.setSafeAreaInsetsOverride, den Playwright über eine CDP-Session senden kann.

---

Quelle: https://wingo-ui.com/de/blog/tailwind-safe-area-insets
