# 100vh mobile fixen: dvh, svh oder lvh in Tailwind

> 100vh mobile Bug erklärt: warum h-screen deine Bottom Bar versteckt, wann du dvh, svh oder lvh in Tailwind nimmst und wie eine Leiste über der Tastatur bleibt.

- 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/100vh-mobile-dvh-tailwind
- Englische Fassung: https://wingo-ui.com/blog/100vh-mobile-dvh-tailwind

## Kurz gesagt

Auf Smartphones wird 100vh (Tailwinds h-screen) mit eingeklappten Browserleisten gemessen. Es ist also höher als der sichtbare Bildschirm, und der untere Teil des Layouts liegt unter der Adressleiste. In Tailwind nimmst du min-h-dvh oder h-dvh für App Shells, eine Obergrenze wie max-h-[92dvh] für Bottom Sheets, min-h-svh für Heros, die wegscrollen, h-lvh nur für fixierte Hintergründe und fixed inset-0 für Overlays. Standardmäßig schrumpft keine Viewport-Einheit für die Bildschirmtastatur. Eine Leiste, die darüber bleiben muss, braucht deshalb window.visualViewport.

Du gibst einem Chat-Screen `h-screen`, setzt das Eingabefeld ans Ende einer Flex-Spalte, und auf dem iPhone ist es weg. Es liegt unter der Toolbar von Safari, und die ganze Seite scrollt um genau deren Höhe. Das ist der 100vh mobile Bug, und die Browser verhalten sich mit Absicht so. Dieses Tutorial erklärt, was 100vh auf einem Smartphone misst, wann du in Tailwind CSS dvh, svh oder lvh nimmst und wie du eine App Shell in voller Höhe, Sheets und eine tastaturbewusste Leiste baust, die stehen bleiben, wenn sich die Adressleiste bewegt. Der Code nutzt den Next.js App Router und Tailwind v4, dazu die Komponenten von Wingo UI, die denselben Regeln folgen.

## Warum ist 100vh auf dem Smartphone höher als der Bildschirm?

Weil `vh` mit ausgeblendeten Browserleisten gemessen wird. Ein mobiler Browser kennt drei Viewport-Höhen: small, mit ausgeklappter Adressleiste und Toolbar; large, mit eingeklappten Leisten; und dynamic, also die, die gerade gilt. Die [Längenreferenz von MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/length) sagt es klar: Alle Standard-Viewport-Einheiten wie vh und vw entsprechen derzeit ihren Large-Gegenstücken wie lvh und lvw.

Jede Seite lädt mit sichtbaren Leisten. 100vh ist also von Anfang an um deren Höhe größer als der sichtbare Bereich. Tailwinds `h-screen` kompiliert genau zu diesem Wert, `100vh`, in Tailwind 4.3 genauso wie in v3.

Das ist das klassische „100vh Safari Problem“. Seit iOS 15 sitzt die Adressleiste von Safari standardmäßig unten. Der Streifen, den 100vh verdeckt, liegt also genau dort, wo dein wichtigster Button sitzt. Chrome auf Android folgt bewusst demselben Modell: Als Chrome 56 `vh` geändert hat, [schrieb das Chrome-Team](https://developer.chrome.com/blog/url-bar-resizing), das neue Modell solle dem Verhalten von Safari entsprechen.

Auf dem Desktop und in einer Web-App, die auf dem Homescreen installiert ist, kommen und gehen keine Leisten. Dort sind die drei Höhen gleich, und der Bug tritt nie auf.

## Was ist der Unterschied zwischen dvh, svh und lvh?

`svh` ist die Höhe mit sichtbaren Leisten, `lvh` die Höhe mit ausgeblendeten Leisten und `dvh` die, die gerade zutrifft. Alle drei sind Prozentwerte, `100dvh` ist also die volle dynamische Höhe:

| Einheit | dvh | svh | lvh |
| --- | --- | --- | --- |
| Misst | Die aktuell sichtbare Höhe | Die Höhe mit ausgeklappten Leisten | Die Höhe mit eingeklappten Leisten, wie vh |
| Ändert sich beim Scrollen | Ja, wenn sich die Leisten bewegen | Nein | Nein |
| Tailwind-Klassen | `h-dvh`, `min-h-dvh`, `max-h-dvh` | `h-svh`, `min-h-svh`, `max-h-svh` | `h-lvh`, `min-h-lvh`, `max-h-lvh` |
| Nimm sie für | Shells, Sheets, alles, was auf dem Screen bleibt | Heros und erste Screens, die wegscrollen | Fixierte Hintergründe, die nie eine Lücke zeigen dürfen |

Die dynamische Einheit hat zwei Haken. Browser drosseln sie: Laut der [Einführung von web.dev](https://web.dev/blog/viewport-units) aktualisieren sich die Werte nicht mit 60 fps. Und alles, was damit bemessen ist, löst bei jeder Aktualisierung einen Reflow aus. [WebKit-Bug 266835](https://bugs.webkit.org/show_bug.cgi?id=266835), gemeldet gegen iOS 17.2.1, beschreibt Ruckeln beim Scrollen, weil dvh mit der Adressleiste die Größe ändert: Abschnitte in voller dvh-Höhe wachsen erst auf die größere Viewport-Höhe, wenn das Scrollen vorbei ist, und das Layout springt am Ende. Stand Oktober 2026 ist der Report weiterhin offen.

Die Unterstützung ist kein Grund mehr zu warten. Safari 15.4, Firefox 101 und Chrome 108 haben alle drei Einheiten ausgeliefert, und [caniuse](https://caniuse.com/viewport-unit-variants) zählt Stand Oktober 2026 rund 95% der weltweiten Nutzung als unterstützt. Die Klassen für Tailwind dvh, svh und lvh [kamen mit v3.4](https://tailwindcss.com/blog/tailwindcss-v3-4), und Tailwind v4 selbst [setzt](https://tailwindcss.com/docs/compatibility) Safari 16.4, Chrome 111 und Firefox 128 voraus. Ein v4-Projekt braucht also keinen `vh`-Fallback.

## Wann nimmst du dvh, svh oder lvh?

Nimm dvh für alles, was auf dem Screen bleibt, svh für alles, was wegscrollt, lvh nur für fixierte Hintergründe und gar keine Einheit für ein fixiertes Element, das an zwei gegenüberliegenden Kanten hängt. Auf die üblichen Teile einer App übertragen:

- **Root der App Shell:** `min-h-dvh`, wenn das Fenster scrollt, `h-dvh`, wenn eine innere Spalte scrollt.
- **Bottom Sheets, Popover, Menüs:** eine Obergrenze wie `max-h-[92dvh]`.
- **Seitenpanels, Overlays, Vollbildmenüs:** `fixed inset-0` oder `fixed inset-y-0`. Ein fixiertes Element, das über seine Kanten bemessen wird, folgt dem sichtbaren Bereich, wenn sich die Leisten bewegen; Chrome hat dieses Verhalten beibehalten, als es `vh` in Version 56 geändert hat.
- **Marketing-Hero oder erster Screen:** `min-h-svh`. Er füllt beim Laden den Screen mit sichtbaren Leisten und wächst beim Lesen nie unter dem Daumen.
- **Fixiertes Hintergrundbild oder Canvas:** `fixed inset-x-0 top-0 h-lvh`. So hoch, wie der Screen werden kann, deckt es nie eine Lücke auf und ändert nie seine Größe.

Damit ist die Frage 100dvh vs 100vh entschieden: Wir haben keinen Fall gefunden, in dem einfaches `vh` die bessere Wahl ist. Und wenn du genau dieses Verhalten willst, macht `lvh` es ausdrücklich. Keine Komponente von Wingo UI nutzt `h-screen` oder bemisst ein Layout in voller Höhe in `vh`; die Komponenten in voller Höhe nutzen `min-h-dvh`, `h-dvh` und dvh-Obergrenzen.

Wenn eine bestehende Codebasis voller `h-screen` ist, kannst du die Klasse zentral an einer Stelle umstellen. In Tailwind v4 hängt ein eigenes Utility mit dem Namen eines Core-Utilitys seine Deklarationen hinter die des Cores:

```css
/* app/globals.css: eine Notlösung, die jedes h-screen zu dvh macht */
@utility h-screen {
  height: 100dvh;
}
@utility min-h-screen {
  min-height: 100dvh;
}
```

Mit Tailwind 4.3.3 kompiliert das `h-screen` und `md:h-screen` zu `height: 100vh; height: 100dvh;`, ein Browser ohne dvh behält also den alten Wert. Es macht aber auch jeden Hero zu einem dvh-Hero. Räum deshalb hinterher auf: Such nach `h-screen` und wähl für jede Stelle eine Einheit.

## Wie baust du eine App Shell in voller Höhe, die nicht springt?

Lass das Dokument scrollen, gib der Shell `min-h-dvh`, fixier den Header mit `sticky` und die Tab Bar mit `fixed`. Dann hängt nichts auf der Seite von der Höhe der Toolbar ab, und nichts bewegt sich, wenn sie sich ändert.

### Schritt 1: Lass die Seite bis an die Bildschirmränder reichen

Schalte den Bereich um die Notch und den Home-Indikator frei, damit `env(safe-area-inset-*)` echte Werte liefert. Im App Router ist das der `viewport`-Export des Root-Layouts:

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

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

Der [Guide zu Tailwind Safe Areas](https://wingo-ui.com/de/blog/tailwind-safe-area-insets) behandelt die Insets ausführlich; hier polstern sie nur die Leisten.

### Schritt 2: eine Shell, die das Fenster scrollt

```tsx
// app/(app)/layout.tsx
import type { ReactNode } from "react";

export default function AppLayout({ children }: { children: ReactNode }) {
  return (
    <div className="flex min-h-dvh flex-col bg-background">
      <header className="sticky top-0 z-40 bg-background/90 pt-[env(safe-area-inset-top)] backdrop-blur">
        <div className="flex h-14 items-center px-4 font-semibold">Posteingang</div>
      </header>
      {/* Platz für die fixierte Tab Bar, damit die letzte Zeile darüber scrollen kann */}
      <main className="flex-1 px-4 pb-[calc(64px+env(safe-area-inset-bottom))]">{children}</main>
      <nav
        aria-label="Hauptnavigation"
        className="fixed inset-x-0 bottom-0 z-40 flex h-[calc(64px+env(safe-area-inset-bottom))] justify-around border-t border-border bg-background pb-[env(safe-area-inset-bottom)]"
      >
        {/* Tab-Links */}
      </nav>
    </div>
  );
}
```

`min-h-dvh` wirkt nur, wenn die Seite kürzer als der Screen ist. Dann endet der Inhalt am unteren Rand des sichtbaren Bereichs, und eine so kurze Seite scrollt nicht, die Leisten bleiben also stehen. Eine längere Seite ist ohnehin höher als das Minimum. Die Leisten kommen und gehen beim Scrollen, ohne die Höhe der Seite zu ändern, und die fixierte Tab Bar folgt dem sichtbaren Bereich von selbst.

Window Scroll ist außerdem das Setup, in dem Safari seine Leisten zuverlässig minimiert. Mit einem inneren Scroll-Container hat sich dieses Verhalten zwischen iOS-Releases geändert: Die WebKit-Bugs [228278](https://bugs.webkit.org/show_bug.cgi?id=228278) und [231878](https://bugs.webkit.org/show_bug.cgi?id=231878), beide aus dem iOS-15-Zyklus, beschreiben gegensätzliches Verhalten, und beide wurden als beabsichtigt geschlossen. Rechne dort nicht mit dem zusätzlichen Platz.

### Schritt 3: eine Shell mit eigenem Scrollbereich

Ein Chat oder ein Board braucht Header und Eingabefeld jederzeit auf dem Screen. Mach die Spalte genau so hoch wie den sichtbaren Bereich und lass ein Kind scrollen:

```tsx
import type { ReactNode } from "react";

export function ChatLayout({ messages, composer }: { messages: ReactNode; composer: ReactNode }) {
  return (
    <div className="flex h-dvh flex-col overflow-hidden bg-background">
      <header className="shrink-0 border-b border-border pt-[env(safe-area-inset-top)]">
        <div className="flex h-14 items-center px-4 font-semibold">Emma Carter</div>
      </header>
      <div className="min-h-0 flex-1 overflow-y-auto overscroll-contain px-4 py-3">{messages}</div>
      <footer className="shrink-0 border-t border-border px-3 pt-2 pb-[max(env(safe-area-inset-bottom),12px)]">
        {composer}
      </footer>
    </div>
  );
}
```

Das ist der kaputte Screen aus der Einleitung, mit `h-dvh` statt `h-screen`. `min-h-0` erlaubt dem mittleren Kind zu schrumpfen, sodass es scrollt, statt das Eingabefeld nach unten zu schieben.

Die [App Shell](https://wingo-ui.com/components/app-shell) baut beide Varianten über eine Prop. `scroll="window"`, der Standard, rendert den Root mit `min-h-dvh`; `scroll="content"` rendert `h-dvh overflow-hidden`. Bei Window Scroll ist die Desktop-Sidebar `sticky top-0 h-dvh`, sodass ihr Footer auch auf einer langen Seite sichtbar bleibt. Die Doku begründet den Standard so: Window Scroll erhält die Scroll-Wiederherstellung des Routers und das Minimieren der iOS-Toolbar.

```tsx
// app/(app)/layout.tsx
import type { ReactNode } from "react";
import { House, Inbox, Settings, Users } from "lucide-react";
import { AppShell } from "@/components/ui/app-shell";
import type { SidebarNavGroup } from "@/components/ui/sidebar";
import { Topbar } from "@/components/ui/topbar";
import type { NavItem } from "@/lib/navigation";

const nav: SidebarNavGroup[] = [
  {
    items: [
      { label: "Start", href: "/", icon: <House /> },
      { label: "Posteingang", href: "/inbox", icon: <Inbox /> },
      { label: "Kunden", href: "/clients", icon: <Users /> },
      { label: "Einstellungen", href: "/settings", icon: <Settings /> },
    ],
  },
];
const tabs: NavItem[] = nav[0].items;

export default function AppLayout({ children }: { children: ReactNode }) {
  // "content" für einen Chat oder ein Board; der Standard "window" lässt Safari seine Leisten minimieren
  return (
    <AppShell nav={nav} tabs={tabs} topbar={<Topbar title="Summit CRM" />} scroll="window">
      {children}
    </AppShell>
  );
}
```

## Wie hoch sollte ein Bottom Sheet auf dem Smartphone sein?

Höchstens 92dvh: hoch genug für eine lange Liste, kurz genug, um oben einen Streifen der abgedunkelten Seite zu lassen, und in dvh, damit die Adressleiste den Griff nie aus dem Screen schiebt. Solange ein modales Sheet offen ist, ist die Seite dahinter gesperrt, die Leisten bewegen sich also selten, und wenn doch, folgt ihnen eine dvh-Obergrenze.

Seitenpanels brauchen gar keine Einheit: Das kostenlose [Sheet](https://wingo-ui.com/components/sheet) platziert sie mit `fixed inset-y-0` und polstert oben mit `env(safe-area-inset-top)`. Bei Bottom Sheets entscheiden zwei Details über die Obergrenze:

- **Die Einheit.** Eine Obergrenze von `92vh` sind 92% des Large Viewport. Sobald die Leisten mehr als 8% des Screens belegen, ist das höher als der sichtbare Bereich, und Griff und Titel beginnen oberhalb des oberen Bildschirmrands.
- **Die 8% Abstand.** Der Streifen abgedunkelter Seite über dem Sheet zeigt, dass es eine Ebene ist, gibt den Leuten eine Fläche zum Antippen und Schließen und hält den Griff von der Statusleiste fern.

In reinem Tailwind ist das `fixed inset-x-0 bottom-0 flex max-h-[92dvh] flex-col` auf dem Panel, `min-h-0 flex-1 overflow-y-auto` auf dem Body, damit eine lange Liste innerhalb der Obergrenze scrollt, und `pb-[max(env(safe-area-inset-bottom),16px)]` auf dem letzten Element. Der [Guide zum React Bottom Sheet](https://wingo-ui.com/de/blog/react-bottom-sheet-for-the-web) baut dieses Layout mit vaul, Drag und Fokus-Handling.

Der kostenlose [Drawer](https://wingo-ui.com/components/drawer) ist genau dieses Layout auf Basis von vaul, mit Drag zum Schließen, Fokus-Handling und dem Safe-Area-Padding schon eingebaut. Seine Sheets wachsen mit ihrem Inhalt bis `max-h-[92dvh]`, und `height="full"` hält sie auf `h-[92dvh]`, auch wenn eine Suche die Liste verkürzt:

```tsx
import { Button } from "@/components/ui/button";
import { Drawer, DrawerClose } from "@/components/ui/drawer";

export function Filters({ results }: { results: number }) {
  return (
    <Drawer
      title="Filter"
      description="New York · 1.240 Angebote"
      height="full"
      trigger={<Button variant="soft">Filter</Button>}
      footer={
        <DrawerClose asChild>
          <Button size="lg" fullWidth>
            {results} Ergebnisse anzeigen
          </Button>
        </DrawerClose>
      }
    >
      <p className="text-sm text-muted-foreground">Preis, Zustand und Kategorien</p>
    </Drawer>
  );
}
```

> Live-Demo (Drawer): Tippe auf Filters: Das Sheet fährt auf 92% der sichtbaren Höhe hoch und behält sie, während du die Kategorien durchsuchst, darüber bleibt ein Streifen der abgedunkelten Seite. Zieh es nach unten oder tippe auf diesen Streifen, um es zu schließen. Probier sie unter [Drawer](https://wingo-ui.com/components/drawer) aus und installier sie mit `npx wingo-ui@latest add drawer`.

Wenn ein Sheet den ganzen Screen bedecken muss, gib ihm `h-dvh` und polstere den oberen Inset. Genau das macht das Sheet als Bottom Sheet mit `size="full"`. Seine kleineren Größen enden bei 50, 70, 85 oder 92 dvh.

> Live-Demo (Sheet): Tippe auf Filters: Dieses Bottom Sheet hat die Größe full, füllt also genau die sichtbare Höhe und polstert die Statusleiste; schließ es mit dem X oder mit Esc. Probier sie unter [Sheet](https://wingo-ui.com/components/sheet) aus und installier sie mit `npx wingo-ui@latest add sheet`.

## Ändert sich dvh, wenn die Bildschirmtastatur aufgeht?

Nein. Safari auf iOS, [Chrome für Android seit Version 108](https://developer.chrome.com/blog/viewport-resize-behavior) und [Firefox für Android seit Version 132](https://www.firefox.com/firefox/android/132.0/releasenotes/) verkleinern beim Öffnen der Tastatur nur den Visual Viewport. Der Layout Viewport behält seine Größe, also behalten svh, dvh und lvh ihre Werte, und die Tastatur verdeckt den unteren Teil deines `h-dvh`-Layouts.

Auf Android kannst du umschalten: `interactiveWidget: "resizes-content"` im `viewport`-Export von Next.js (der Schlüssel `interactive-widget` im Viewport-Meta-Tag) lässt die Tastatur den Layout Viewport verkleinern und jede Viewport-Einheit mit ihm. Das Chat-Layout von oben hält sein Eingabefeld dann ohne JavaScript über der Tastatur. Stand Oktober 2026 listet [caniuse](https://caniuse.com/mdn-html_elements_meta_name_viewport_interactive-widget) keine Unterstützung in Safari, iPhones brauchen also weiterhin Code.

Dieser Code liest `window.visualViewport` aus: Solange ein Textfeld den Fokus hat, verdeckt die Tastatur `innerHeight - visualViewport.height - visualViewport.offsetTop` Pixel. Der [Hook useKeyboardInset](https://wingo-ui.com/components/use-keyboard-inset) misst das höchstens einmal pro Frame und gibt es als Motion Value zurück, dazu ein `open`-Flag, das nur neu rendert, wenn es umschlägt. Wenn du nur mit CSS arbeitest, schreibt er den Wert auch in eine Variable auf `<html>`:

```tsx
"use client";

import { useKeyboardInset } from "@/hooks/use-keyboard-inset";

// einmal nahe dem Root mounten: --keyboard-inset ist die verdeckte Höhe in px, 0 ohne Tastatur
export function KeyboardInsetVar() {
  useKeyboardInset({ cssVar: "--keyboard-inset" });
  return null;
}
```

```tsx
<form className="fixed inset-x-0 bottom-0 translate-y-[calc(var(--keyboard-inset,0px)*-1)] bg-background px-3 pt-2 pb-[max(env(safe-area-inset-bottom),12px)]">
  {/* Nachrichtenfeld und Senden-Button */}
</form>
```

Die Leiste bewegt sich per Transform. Es gibt also keinen Reflow, während die Tastatur hereingleitet. Der Hook meldet 0 auf dem Server, auf dem Desktop, beim Pinch-Zoom und immer dann, wenn kein Textfeld den Fokus hat. Eine verdeckte Höhe von 80px oder weniger (sein `threshold`) ignoriert er, etwa die Formular-Zubehörleiste von iOS allein. In einem Bottom Sheet brauchst du nichts davon: `repositionInputs` des Drawers, standardmäßig an, schiebt das Sheet nach oben, damit das fokussierte Feld sichtbar bleibt. Den Rest eines Chat-Screens, vom Auto-Scroll beim Streamen einer Antwort bis zu Enter während einer IME-Eingabe, findest du in unserem [Guide zur React AI Chat UI](https://wingo-ui.com/de/blog/react-ai-chat-ui).

> Live-Demo (useKeyboardInset): Tippe auf dem Smartphone in das Nachrichtenfeld: Das Eingabefeld fährt auf der Tastatur mit, und die Anzeige zeigt die verdeckte Höhe. Auf dem Desktop gibt es keine Bildschirmtastatur, der Inset bleibt also bei 0px. Probier sie unter [useKeyboardInset](https://wingo-ui.com/components/use-keyboard-inset) aus und installier sie mit `npx wingo-ui@latest add use-keyboard-inset`.

## Solltest du den --vh-Hack mit JavaScript noch nutzen?

Nein. Der Hack setzt in einem Resize-Listener eine Variable `--vh` aus `window.innerHeight`. Bis dieses Skript läuft, nutzt die serverseitig gerenderte Seite den Fallback, das Layout kann beim Laden also springen. Danach misst er neu, wenn sich die Leisten bewegen, und springt beim Scrollen genauso wie dvh, nur mit einem zusätzlichen Umweg über den Main Thread. Jeder Browser, den Tailwind v4 unterstützt, hat die nativen Einheiten. Lösch den Hack also, und `height: -webkit-fill-available` gleich mit.

## Wie testest du Viewport-Höhen auf einem echten Smartphone?

Auf dem Smartphone selbst, mit einer Messanzeige auf der Seite. Die Geräteemulation im Desktop-Browser hat keine Leisten, die einklappen. Dort sind die drei Einheiten gleich, und jeder Bug aus diesem Artikel bleibt verborgen. Diese Client Component zeigt die drei Höhen, `innerHeight` und den Visual Viewport:

```tsx
"use client";

import { useEffect, useRef, useState } from "react";

const UNITS = ["svh", "dvh", "lvh"] as const;

export function ViewportProbe() {
  const probes = useRef<Partial<Record<(typeof UNITS)[number], HTMLDivElement | null>>>({});
  const [text, setText] = useState("");

  useEffect(() => {
    const visual = window.visualViewport;
    const read = () => {
      const units = UNITS.map((unit) => `${unit} ${probes.current[unit]?.offsetHeight ?? 0}`);
      setText([...units, `inner ${window.innerHeight}`, `visual ${Math.round(visual?.height ?? 0)}`].join(" · "));
    };
    read();
    window.addEventListener("resize", read);
    visual?.addEventListener("resize", read);
    return () => {
      window.removeEventListener("resize", read);
      visual?.removeEventListener("resize", read);
    };
  }, []);

  return (
    <>
      {UNITS.map((unit) => (
        <div
          key={unit}
          ref={(node) => {
            probes.current[unit] = node;
          }}
          aria-hidden
          className="pointer-events-none invisible fixed top-0 left-0 w-px"
          style={{ height: `100${unit}` }}
        />
      ))}
      <output className="fixed top-[env(safe-area-inset-top)] left-2 z-50 rounded-md bg-foreground px-2 py-1 font-mono text-xs text-background">
        {text}
      </output>
    </>
  );
}
```

Dann, auf einem iPhone und auf einem Android-Smartphone: Lade die Seite und prüf, dass nichts Wichtiges unter der Toolbar liegt. Scroll, bis die Leisten minimiert sind, und zurück, und achte auf Inhalt, der springt. Öffne jedes Sheet und such seinen Griff und seinen letzten Button. Fokussiere ein Textfeld und schau, was die Tastatur verdeckt. Und dreh das Gerät ins Querformat, wo der Screen niedrig ist und jeder Pixel Toolbar zählt.

## Wie behebst du 100vh in einer bestehenden App?

Such in deinem Code nach `h-screen` und `100vh` und gib jedem Treffer die Einheit für seine Aufgabe: `min-h-dvh` oder `h-dvh` für Shells, `min-h-svh` für Heros, `inset-0` für fixierte Overlays. Installier dann den kostenlosen Drawer und das Sheet mit `npx wingo-ui@latest add drawer sheet`; beide begrenzen ihre Höhe in dvh und polstern die Safe Areas. Die App Shell und der Hook useKeyboardInset gehören zu [Wingo UI Pro](https://wingo-ui.com/de/pricing). Für die übrigen Regeln auf dem Smartphone, von Bottom Sheets bis zu Touch Targets, lies den [Guide zu Mobile-first React-Komponenten](https://wingo-ui.com/de/blog/mobile-first-react-components) oder stöbere in [Mobile UI in React](https://wingo-ui.com/de/blog/category/mobile-ui).

## Komponenten in diesem Artikel

- [Sheet](https://wingo-ui.com/components/sheet) (Free): A React sheet that slides in from any edge on a spring for filters, a cart, an inspector or phone navigation, swipeable to close on touch. Installation: `npx wingo-ui@latest add sheet`
- [Drawer](https://wingo-ui.com/components/drawer) (Free): A React drawer and bottom sheet built on vaul: drag to dismiss, snap points, nesting, and a side panel or dialog on desktop. Installation: `npx wingo-ui@latest add drawer`
- [App Shell](https://wingo-ui.com/components/app-shell) (Pro): A React app shell for admin layouts: a collapsible, resizable sidebar, a top bar and the page, with a drawer and a tab bar on phones. Installation: `npx wingo-ui@latest add app-shell`
- [useKeyboardInset](https://wingo-ui.com/components/use-keyboard-inset) (Pro): A React hook for the mobile on-screen keyboard: how much of the screen it covers, so bars pinned to the bottom ride above it. Installation: `npx wingo-ui@latest add use-keyboard-inset`

## Häufige Fragen

### Warum ist 100vh auf dem Smartphone höher als der Bildschirm?

Alle großen Browser berechnen vh gegen den Large Viewport, also die Größe mit eingeklappter Adressleiste und Toolbar. Solange die Leisten sichtbar sind, ragt 100vh um ihre Höhe über den sichtbaren Bereich hinaus, und ein Button am unteren Rand eines h-screen-Layouts landet unter der Toolbar.

### Was ist der Unterschied zwischen 100dvh und 100vh?

100vh ist fest auf die Höhe des Large Viewport gesetzt. 100dvh folgt den Leisten: Es entspricht 100svh, solange sie ausgeklappt sind, und 100lvh, sobald sie einklappen. Damit folgt es der sichtbaren Höhe, ändert aber seine Größe, während die Seite scrollt.

### Unterstützt Tailwind CSS dvh?

Ja. Seit Tailwind CSS v3.4 gibt es h-dvh, h-svh und h-lvh samt min-h- und max-h-Varianten, und Arbitrary Values wie max-h-[92dvh] funktionieren auch. h-screen kompiliert in Tailwind 4.3 weiterhin zu height: 100vh.

### Ändert sich dvh, wenn die Bildschirmtastatur aufgeht?

Standardmäßig nicht. Safari auf iOS, Chrome für Android seit Version 108 und Firefox für Android seit Version 132 verkleinern für die Tastatur nur den Visual Viewport, die Viewport-Einheiten behalten also ihre Werte. Android-Browser lassen dich mit interactive-widget=resizes-content umschalten; auf iOS liest du window.visualViewport.

### Wird dvh in Safari unterstützt?

Ja, seit Safari 15.4 auf macOS und iOS. Chrome und Edge haben die Einheiten mit Version 108 eingeführt, Firefox mit Version 101, und die eigene Baseline von Tailwind CSS v4 (Safari 16.4, Chrome 111, Firefox 128) ist neuer als all diese Versionen.

### Sind Sheet und Drawer von Wingo UI kostenlos?

Ja. Sheet und Drawer sind kostenlos, begrenzen ihre Höhe bereits in dvh und polstern die Safe Areas. Die App Shell und der Hook useKeyboardInset gehören zu Pro.

---

Quelle: https://wingo-ui.com/de/blog/100vh-mobile-dvh-tailwind
