Zum Inhalt springen
    WingoUI
    KomponentenVorlagenDokuPreiseKI-AgentenBlogChangelog
    1. Startseite
    2. Blog
    3. Mobile UI mit React
    4. 100vh mobile fixen: dvh, svh oder lvh in Tailwind
    Read in English
    1. Blog
    2. Mobile UI mit React
    3. 100vh mobile fixen: dvh, svh oder lvh in Tailwind
    Mobile UI mit React
    Mobile UI mit React

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

    SR
    Serban Rusu · Gründer von Wingo UI
    9. Okt. 2026 · 11 Min. Lesezeit

    Auf dieser Seite

    0%
    1. Warum ist 100vh auf dem Smartphone höher als der Bildschirm?
    2. Was ist der Unterschied zwischen dvh, svh und lvh?
    3. Wann nimmst du dvh, svh oder lvh?
    4. Wie baust du eine App Shell in voller Höhe, die nicht springt?
      1. Schritt 1: Lass die Seite bis an die Bildschirmränder reichen
      2. Schritt 2: eine Shell, die das Fenster scrollt
      3. Schritt 3: eine Shell mit eigenem Scrollbereich
    5. Wie hoch sollte ein Bottom Sheet auf dem Smartphone sein?
    6. Ändert sich dvh, wenn die Bildschirmtastatur aufgeht?
    7. Solltest du den --vh-Hack mit JavaScript noch nutzen?
    8. Wie testest du Viewport-Höhen auf einem echten Smartphone?
    9. Wie behebst du 100vh in einer bestehenden App?
    10. Häufige Fragen
      1. Warum ist 100vh auf dem Smartphone höher als der Bildschirm?
      2. Was ist der Unterschied zwischen 100dvh und 100vh?
      3. Unterstützt Tailwind CSS dvh?
      4. Ändert sich dvh, wenn die Bildschirmtastatur aufgeht?
      5. Wird dvh in Safari unterstützt?
      6. Sind Sheet und Drawer von Wingo UI kostenlos?

    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.

    Veröffentlicht am 9. Okt. 2026

    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 (opens in a new tab) 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 (opens in a new tab), 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:

    Einheitdvhsvhlvh
    MisstDie aktuell sichtbare HöheDie Höhe mit ausgeklappten LeistenDie Höhe mit eingeklappten Leisten, wie vh
    Ändert sich beim ScrollenJa, wenn sich die Leisten bewegenNeinNein
    Tailwind-Klassenh-dvh, min-h-dvh, max-h-dvhh-svh, min-h-svh, max-h-svhh-lvh, min-h-lvh, max-h-lvh
    Nimm sie fürShells, Sheets, alles, was auf dem Screen bleibtHeros und erste Screens, die wegscrollenFixierte 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 (opens in a new tab) 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 (opens in a new tab), 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 (opens in a new tab) 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 (opens in a new tab), und Tailwind v4 selbst setzt (opens in a new tab) 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 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 (opens in a new tab) und 231878 (opens in a new tab), 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 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 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 baut dieses Layout mit vaul, Drag und Fokus-Handling.

    Der kostenlose 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>
    );
    }
    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
    $ npx wingo-ui@latest add drawer
    FreeDoku: 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.

    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
    $ npx wingo-ui@latest add sheet
    FreeDoku: Sheet

    Ändert sich dvh, wenn die Bildschirmtastatur aufgeht?

    Nein. Safari auf iOS, Chrome für Android seit Version 108 (opens in a new tab) und Firefox für Android seit Version 132 (opens in a new tab) 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 (opens in a new tab) 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 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.

    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
    $ npx wingo-ui@latest add use-keyboard-inset
    ProDoku: useKeyboardInset

    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. Für die übrigen Regeln auf dem Smartphone, von Bottom Sheets bis zu Touch Targets, lies den Guide zu Mobile-first React-Komponenten oder stöbere in Mobile UI in React.

    Komponenten in diesem Artikel

    • Sheet

      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.

      Free
    • Drawer

      A React drawer and bottom sheet built on vaul: drag to dismiss, snap points, nesting, and a side panel or dialog on desktop.

      Free
    • App Shell

      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.

      Pro
    • useKeyboardInset

      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.

      Pro

    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.

    • Mobile UI
    • Tailwind CSS
    • CSS
    • React

    Teilen

    SR

    Über den Autor

    Serban Rusu

    Gründer von Wingo UI

    Serban Rusu hat Wingo UI gegründet. Er baut die Komponenten, die CLI und den MCP-Server und schreibt über React-UIs für Smartphones und KI-Agents.

    Mehr von Serban
    Weiterlesen

    Ähnliche Artikel

    Alle Artikel
    Mobile UI mit React

    iOS Safari Tastatur position fixed: Composer sichtbar halten

    Fix für iOS Safari Tastatur position fixed: Miss die Tastatur per visualViewport und heb Chat-Composer oder Bottom Sheet per Motion Value über die Tasten.

    SRSerban Rusu·13. Okt. 2026·9 Min. Lesezeit
    Mobile UI mit React

    React Bottom Sheet fürs Web: Snap Points, Drag, Tastatur

    So baust du ein React Bottom Sheet fürs Web mit vaul: Snap Points, Drag to Dismiss, verschachtelte Sheets, Bildschirmtastatur und Home-Indikator, mit Code.

    SRSerban Rusu·9. Okt. 2026·9 Min. Lesezeit
    Mobile UI mit React

    Shadcn Responsive Dialog: Karte am Desktop, Sheet am Handy

    Bau einen shadcn responsive dialog nur einmal: zentrierte Karte ab 768px, ziehbares Bottom Sheet darunter, ein einziger Inhalt und kein Hydration-Flackern.

    SRSerban Rusu·9. Okt. 2026·10 Min. Lesezeit
    Newsletter

    Neue Artikel per E-Mail

    Neue Guides, Tutorials und Vergleiche aus dem Wingo UI Blog, sobald sie erscheinen. Die meisten Artikel sind auf Englisch.

    Kein Spam. Du kannst dich jederzeit abmelden.

    WingoUI

    Animierte, konfigurierbare, mobile-first React-Komponenten und Blöcke. Kopier den Quellcode, pass ihn an und lass deinen Coding-Agenten damit bauen.

    Produkt

    • Komponenten
    • Vorlagen
    • Preise
    • KI-Agenten
    • Theme-Generator

    Ressourcen

    • Doku
    • Blog
    • Changelog
    • Changelog-RSS
    • llms.txt

    Rechtliches

    • AGB
    • Datenschutz
    • Rückerstattung
    • Lizenz
    • Impressum

    Komponenten-Kategorien

    • Buttons & Actions
    • Inputs
    • Forms
    • Navigation
    • Overlays
    • Feedback
    • Data Display
    • Tables & Lists
    • Charts & Stats
    • Layout
    • Media
    • AI Kit
    • Text & Effects
    • Mobile
    • Commerce
    • Marketing Sections
    • Blocks
    • Hooks & Utilities

    © 2026 Wingo UI

    Wingo UI