Zum Inhalt springen
    WingoUI
    KomponentenVorlagenDokuPreiseKI-AgentenBlogChangelog
    1. Startseite
    2. Blog
    3. Mobile UI mit React
    4. Shadcn Responsive Dialog: Karte am Desktop, Sheet am Handy
    Read in English
    1. Blog
    2. Mobile UI mit React
    3. Shadcn Responsive Dialog: Karte am Desktop, Sheet am Handy
    Mobile UI mit React
    Mobile UI mit React

    Shadcn Responsive Dialog: Karte am Desktop, Sheet am Handy

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

    Auf dieser Seite

    0%
    1. Was macht das Rezept für den shadcn responsive dialog?
    2. Wo sollte die Umschaltung zwischen Dialog und Drawer sitzen?
    3. Wie vermeidest du ein Hydration-Flackern mit useMediaQuery?
    4. Wie baust du das mit dem Dialog von Wingo UI?
      1. 1. Den Dialog installieren
      2. 2. Den Inhalt einmal schreiben
      3. 3. Bei beiden Breiten öffnen
      4. 4. Formular-State über dem Dialog halten
      5. 5. Abschalten, wo ein Sheet nicht passt
    5. Shadcn drawer vs sheet: Was solltest du nehmen?
    6. Modal vs bottom sheet: Wann sollte ein Handy trotzdem eine zentrierte Karte bekommen?
    7. Was solltest du als Nächstes tun?
    8. Häufige Fragen
      1. Wie mache ich einen shadcn Dialog auf dem Handy responsive?
      2. Was ist der Unterschied zwischen dem shadcn Drawer und dem Sheet?
      3. Warum zeigt mein responsiver Dialog kurz die falsche Komponente?
      4. Ab welcher Breite sollte ein Dialog zum Bottom Sheet werden?
      5. Ist der Dialog von Wingo UI kostenlos?

    Kurz gesagt

    Ein shadcn responsive dialog rendert ab 768px den Dialog und darunter den Drawer, ein Bottom Sheet, das du nach unten ziehen kannst. Leg diese Umschaltung in eine einzige Root-Komponente, deren Teile den Modus aus dem Context lesen: So schreibst du den Inhalt nur einmal, und der offene Zustand übersteht ein Resize. Lies den Breakpoint mit useSyncExternalStore, damit Server und Hydration-Render übereinstimmen, und halte alles, was vor dem ersten Antippen sichtbar ist, in beiden Modi gleich, damit nichts flackern kann.

    Veröffentlicht am 9. Okt. 2026

    Der shadcn responsive dialog ist das Muster aus der Drawer-Doku von shadcn/ui: am Desktop ein zentrierter Dialog, auf dem Handy ein Drawer, also ein Bottom Sheet, das du nach unten ziehen kannst. Das Rezept funktioniert. Aber du schreibst Trigger und Header doppelt, und der Zweig wird von einem Hook gewählt, der beim ersten Render rät. Beides führt zu Bugs, sobald du einen shadcn Dialog mobile-tauglich machst: Die Kopien laufen auseinander, und der Zweig kippt nach dem Mount. Dieses Tutorial baut eine einzige Dialog-API, die ab 768px eine Karte und darunter ein ziehbares Sheet rendert. Den Inhalt schreibst du einmal, und während der Hydration flackert nichts.

    Was macht das Rezept für den shadcn responsive dialog?

    Es rendert zwei komplette Bäume und wählt einen per Media Query. Stand Oktober 2026 schlägt die Drawer-Doku von shadcn (opens in a new tab) vor, Dialog und Drawer zu einem responsiven Dialog zu kombinieren, und das Beispiel drawer-dialog (opens in a new tab) sieht etwa so aus:

    tsx
    const [open, setOpen] = React.useState(false)
    const isDesktop = useMediaQuery("(min-width: 768px)")
    if (isDesktop) {
    return <Dialog open={open} onOpenChange={setOpen}>{/* Trigger, Header, Formular */}</Dialog>
    }
    return <Drawer open={open} onOpenChange={setOpen}>{/* Trigger, Header, Formular, Footer */}</Drawer>

    Dass open über dem Zweig liegt, ist richtig: Ein Resize über 768px hinweg lässt den Dialog offen. Drei Dinge kosten dich später:

    1. Zwei Kopien. Trigger, Titel und Beschreibung stehen in beiden Zweigen, und der Cancel-Footer existiert nur im Drawer. Jede Textänderung musst du an zwei Stellen einpflegen.
    2. Eine Vermutung bei jedem Mount. Der useMediaQuery (opens in a new tab) des Beispiels startet mit useState(false) und liest matchMedia in einem Effect. Das ergibt auf dem Server false, was in Ordnung ist, aber auch beim ersten Client-Render jedes späteren Mounts. Am Desktop rendert also jeder Mount zuerst den Drawer-Baum und tauscht ihn nach dem Effect aus. Ein Dialog, der schon offen gemountet wird (etwa über eine URL mit ?edit=1), startet als falsche Komponente.
    3. Ein Remount bei jedem Wechsel. Dialog und Drawer sind verschiedene Komponententypen. Ein Wechsel unmountet also den einen Baum und mountet den anderen, samt allem State darin.

    Fertige Wrapper lösen den ersten Punkt. Stand Oktober 2026 packen Credenza (opens in a new tab) (MIT) und der Responsive Dialog von Dice UI (opens in a new tab) einen Dialog und einen Vaul-Drawer in ein gemeinsames Set von Teilen. Credenza schaltet bei 768px um, und auch sein useIsMobile startet mit false und kippt in einem Effect. Das zweite Problem bleibt also. Wenn du beim Standard-shadcn/ui bleiben willst, nimm einen davon oder den Wrapper unten, und prüf, was sein Hook zurückgibt, bevor der erste Effect läuft.

    Wo sollte die Umschaltung zwischen Dialog und Drawer sitzen?

    In der Root, und nur dort. Die Root liest den Breakpoint, hält den offenen Zustand und sagt jedem Teil, in welchem Modus es ist. Jedes Teil rendert dann das Dialog-Stück oder das Drawer-Stück. Das klappt, weil Trigger, Close, Title und Description von Vaul die Teile von Radix Dialog selbst sind. Der Trigger ist also in beiden Modi derselbe Button.

    Hier ist dieser Wrapper für einen responsive dialog drawer auf Basis von Dialog und Drawer aus dem Standard-shadcn/ui (Radix-Versionen, Stand Oktober 2026 gegen ihren Quellcode typgeprüft):

    tsx
    "use client";
    import { createContext, useContext, useState, type ComponentType, type ReactNode } from "react";
    import { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
    import { Drawer, DrawerClose, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerTitle, DrawerTrigger } from "@/components/ui/drawer";
    import { useIsMobile } from "@/hooks/use-is-mobile";
    import { cn } from "@/lib/utils";
    const SheetMode = createContext(false);
    export function ResponsiveDialog({ children }: { children: ReactNode }) {
    const sheet = useIsMobile();
    // ein open-State über beiden Bäumen, so bleibt der Dialog offen, wenn die Breite 768px überschreitet
    const [open, setOpen] = useState(false);
    return (
    <SheetMode.Provider value={sheet}>
    {sheet ? (
    <Drawer open={open} onOpenChange={setOpen}>{children}</Drawer>
    ) : (
    <Dialog open={open} onOpenChange={setOpen}>{children}</Dialog>
    )}
    </SheetMode.Provider>
    );
    }
    type PartProps = { asChild?: boolean; className?: string; children?: ReactNode };
    // jedes Teil rendert das Dialog- oder das Drawer-Stück, dazu die Klassen, die der Drawer braucht
    function part(Card: ComponentType<PartProps>, Sheet: ComponentType<PartProps>, sheetClass = "") {
    return function ResponsivePart({ className, ...props }: PartProps) {
    const sheet = useContext(SheetMode);
    const Part = sheet ? Sheet : Card;
    return <Part className={cn(sheet && sheetClass, className)} {...props} />;
    };
    }
    const Div = ({ className, children }: PartProps) => <div className={className}>{children}</div>;
    export const ResponsiveDialogTrigger = part(DialogTrigger, DrawerTrigger);
    export const ResponsiveDialogClose = part(DialogClose, DrawerClose);
    export const ResponsiveDialogContent = part(DialogContent, DrawerContent);
    export const ResponsiveDialogHeader = part(DialogHeader, DrawerHeader);
    export const ResponsiveDialogTitle = part(DialogTitle, DrawerTitle);
    export const ResponsiveDialogDescription = part(DialogDescription, DrawerDescription);
    // der Drawer hat kein Padding im Body und stapelt seinen Footer von oben nach unten
    export const ResponsiveDialogBody = part(Div, Div, "px-4");
    export const ResponsiveDialogFooter = part(DialogFooter, DrawerFooter, "flex-col-reverse");

    Die beiden zusätzlichen Klassen sind wichtig. DrawerContent hat kein eigenes Padding, also braucht der Body im Sheet-Modus px-4. DrawerFooter stapelt seine Buttons von oben nach unten. Ohne flex-col-reverse stünde im Sheet Abbrechen über Speichern. Mit der Klasse schreibst du Abbrechen zuerst und Speichern zuletzt, und Speichern landet in der Karte rechts und im Sheet oben. Den Header lässt du in Ruhe: Der Drawer zentriert ihn mit einem Selektor group-data-[vaul-drawer-direction=bottom], der ein einfaches text-left überstimmt. Deshalb ändert auch das className="text-left" im shadcn-Beispiel nichts.

    Wie vermeidest du ein Hydration-Flackern mit useMediaQuery?

    Sorg dafür, dass die Antwort des Servers und die erste Antwort des Clients gleich sind, und halte alles, was vor dem ersten Antippen sichtbar ist, in beiden Modi identisch. Ein geschlossener Dialog rendert nur seinen Trigger, und dieser Trigger ist in beiden Bäumen derselbe Radix-Button. Der Server kann ihn also rendern, ohne die Bildschirmbreite zu kennen. Der Hook muss während der Hydration nur mit dem Server übereinstimmen und bei jedem Render danach stimmen. Genau das macht useSyncExternalStore:

    ts
    // hooks/use-is-mobile.ts
    import { useSyncExternalStore } from "react";
    const QUERY = "(max-width: 767.98px)";
    function subscribe(onChange: () => void) {
    const list = window.matchMedia(QUERY);
    list.addEventListener("change", onChange);
    return () => list.removeEventListener("change", onChange);
    }
    export function useIsMobile() {
    return useSyncExternalStore(
    subscribe,
    () => window.matchMedia(QUERY).matches,
    // Server und Hydration-Render gehen von einem Desktop aus
    () => false,
    );
    }

    React ruft die dritte Funktion auf dem Server und während der Hydration auf. Beide stimmen also überein, und es gibt keine Mismatch-Warnung. Jeder Render danach, auch der erste Render eines Dialogs, der später gemountet wird, ruft die zweite Funktion auf und bekommt die echte Antwort, ohne Effect dazwischen. Handys korrigieren die Vermutung direkt nach der Hydration: Die Root tauscht Dialog gegen Drawer unter einem geschlossenen Trigger, und auf dem Bildschirm ändert sich nichts. Wenn du die Sidebar von shadcn installiert hast, exportiert hooks/use-mobile.ts bereits ein useIsMobile mit useState und einem Effect. Gib ihm diesen Funktionsrumpf, und die Sidebar bekommt denselben Fix.

    Den Fall „wird später gemountet“ haben wir am Dialog von Wingo UI geprüft, der genau so gebaut ist. Sein Playground lädt die Demo nach der Hydration. Wir haben den Dialog also beim Laden öffnen lassen und die Seite in Chromium über Playwright bei 390px in jedem Animation Frame abgetastet. Ab dem ersten Frame, in dem der Dialog existierte, war er das Sheet, nie die zentrierte Karte, und die Konsole blieb leer.

    Was weiterhin flackert, ist sichtbares Markup, das anhand des Hooks verzweigt, etwa ein Formular, das am Desktop inline steht und auf dem Handy hinter einem Button. Schalte so etwas mit CSS-Breakpoints um. Der Guide zu Mobile-first-React-Komponenten zeigt die richtige und die falsche Version nebeneinander. Der kostenlose Hook useMediaQuery ist dieses Snippet, verallgemeinert auf jede Query, mit einem Parameter serverFallback, und er exportiert dasselbe useIsMobile. Unser Tutorial zum SSR-sicheren React use mobile Hook vergleicht ihn in einer echten Hydration mit den Versionen mit useState und typeof window.

    Wie baust du das mit dem Dialog von Wingo UI?

    Installier den kostenlosen Dialog und schreib ihn einmal: Er erledigt die Umschaltung selbst über die Prop responsive, die standardmäßig true ist. Die Root liest den Breakpoint wie oben über useSyncExternalStore und rendert ab 768px einen Radix-Dialog und darunter das Bottom Sheet aus dem Drawer. Jedes Teil (Header, Titel, Beschreibung, Body, Footer) rendert das passende Stück.

    1. Den Dialog installieren

    bash
    npx wingo-ui@latest add dialog

    Die CLI kopiert components/ui/dialog.tsx und alles, was die Datei importiert, etwa den Drawer, den Media-Query-Hook, den Button und die Motion-Helfer. Alles davon ist kostenlos.

    2. Den Inhalt einmal schreiben

    tsx
    "use client";
    import { useState } from "react";
    import { Button } from "@/components/ui/button";
    import { Dialog, DialogClose } from "@/components/ui/dialog";
    import { Input } from "@/components/ui/input";
    type Profile = { name: string; email: string };
    export function EditProfileDialog({ profile, onSave }: { profile: Profile; onSave: (next: Profile) => Promise<void> }) {
    const [open, setOpen] = useState(false);
    // der Entwurf liegt über dem Dialog, so bleibt das Getippte erhalten, wenn der offene Dialog 768px überschreitet
    const [draft, setDraft] = useState(profile);
    const [saving, setSaving] = useState(false);
    async function save() {
    setSaving(true);
    try {
    await onSave(draft);
    setOpen(false);
    } finally {
    setSaving(false);
    }
    }
    return (
    <Dialog
    open={open}
    onOpenChange={setOpen}
    trigger={
    <Button variant="soft" onClick={() => setDraft(profile)}>
    Profil bearbeiten
    </Button>
    }
    title="Profil bearbeiten"
    description="Dein Name und deine E-Mail stehen auf jeder Rechnung."
    footer={
    <>
    <DialogClose asChild>
    <Button variant="soft">Abbrechen</Button>
    </DialogClose>
    <Button loading={saving} loadingText="Wird gespeichert" onClick={save}>
    Speichern
    </Button>
    </>
    }
    >
    <div className="flex flex-col gap-4">
    <Input label="Name" autoComplete="name" value={draft.name} onValueChange={(name) => setDraft({ ...draft, name })} />
    <Input label="E-Mail" type="email" autoComplete="email" value={draft.email} onValueChange={(email) => setDraft({ ...draft, email })} />
    </div>
    </Dialog>
    );
    }

    Die Hauptaktion kommt zuletzt in den Footer. Am Desktop landet sie rechts; im Sheet auf dem Handy stapeln sich die Buttons in voller Breite mit 48px Höhe, Speichern oben.

    3. Bei beiden Breiten öffnen

    Probier den Playground unten erst am Laptop aus, dann auf dem Handy oder in einem Fenster, das du vor dem Antippen auf unter 768px verkleinerst.

    Tippe auf Edit: Auf einem breiten Bildschirm skaliert die Karte herein und fokussiert das erste Feld; unter 768px öffnet sich derselbe Dialog als Bottom Sheet, das du zum Schließen nach unten ziehst
    $ npx wingo-ui@latest add dialog
    FreeDoku: Dialog

    Unter 768px ändert dasselbe JSX neben dem Layout auch das Verhalten:

    • Schließen. Ein Griff erscheint, das Sheet folgt deinem Finger, und es schließt sich, wenn du es weiter als ein Viertel seiner Höhe ziehst und loslässt oder schnell nach unten wischst.
    • Fokus. Die Karte am Desktop fokussiert das erste Feld. Das Sheet fokussiert sich selbst, damit die Tastatur nicht das halbe Sheet verdeckt, bevor die Person ein Feld wählt; Tab geht von oben weiter.
    • Scrollen. Ein langer Body scrollt zuerst und zieht das Sheet erst, wenn er wieder oben ist, und unten bleibt Platz für den Home Indicator.
    • Größe. size="full" ist auf dem Handy ein Sheet mit 92% Höhe und am Desktop eine Karte mit 16px Abstand zu jedem Rand.

    classNames gelten in beiden Modi, und die Teile des Sheets behalten die Namen data-slot="dialog-*". CSS oder Tests, die auf [data-slot=dialog-footer] zielen, treffen also Karte und Sheet gleichermaßen.

    4. Formular-State über dem Dialog halten

    Beim Überschreiten von 768px bleibt der Dialog offen, aber sein Inhalt wird neu gemountet, und State darin wird zurückgesetzt. Wir haben bei 1024px in das Titelfeld des Playgrounds getippt und das Fenster auf 390px verkleinert: Das Sheet war offen, und das Feld stand wieder auf seinem ursprünglichen Wert. Das shadcn-Beispiel und der Wrapper oben verhalten sich genauso, weil die Root den Komponententyp wechselt. In der Praxis passiert das bei einem Tablet, das über den Breakpoint hinweg gedreht wird, etwa ein iPad mini mit 744px Breite im Hochformat und 1133px im Querformat. Der Fix ist der aus Schritt 2: Halte den Entwurf in der Komponente, die den Dialog rendert. Innerhalb des Dialogs liefert dir useDialog() das Objekt { open, setOpen, sheet }, falls ein Teil im Sheet ein anderes Layout braucht.

    5. Abschalten, wo ein Sheet nicht passt

    Übergib responsive={false}, um auf dem Handy eine zentrierte Karte zu behalten, oder setz es für die ganze App mit <UIProvider defaults={{ dialog: { responsive: false } }}>. Der Alert Dialog wird nie zum Sheet: Auf dem Handy bleibt eine destruktive Frage eine zentrierte Karte mit Buttons in voller Breite übereinander. Er gehört zu Wingo UI Pro.

    Shadcn drawer vs sheet: Was solltest du nehmen?

    Der Drawer ist ein Bottom Sheet mit Ziehgeste; das Sheet ist ein Panel, das mit einer CSS-Animation von einer Kante hereingleitet, ohne Ziehgeste. Beide können von jeder Kante aus öffnen, der Drawer standardmäßig von unten und das Sheet von rechts. Nimm den Drawer oder einen responsiven Dialog für Aufgaben und Auswahlen auf dem Handy, und das Sheet für Inhalte, die am Desktop neben der Seite stehen, etwa Navigation, Filter oder einen Warenkorb. Stand Oktober 2026:

    KomponenteBasiert aufZum Schließen ziehenAb 768px
    shadcn Drawer (opens in a new tab) (Radix)VaulJaDerselbe Drawer
    shadcn Sheet (opens in a new tab)Radix DialogNeinEin Seitenpanel
    Wingo UI DialogRadix Dialog + DrawerAuf dem HandyEine zentrierte Karte
    Wingo UI DrawerVaulJaSheet, Seitenpanel oder Dialog
    Wingo UI SheetRadix Dialog + motionAuf TouchscreensEin Seitenpanel

    Zum selben Zeitpunkt steht im README von Vaul (opens in a new tab), dass das Repo nicht mehr gepflegt wird, und laut der Base-UI-Doku des shadcn Drawer (opens in a new tab) nutzt er inzwischen Base UI statt Vaul. Der Drawer von Wingo UI und damit der Handy-Modus des Dialogs laufen weiterhin auf Vaul 1.1.2. Snap Points, verschachtelte Sheets und die Tastatur in einem Sheet behandelt unser Guide zum React Bottom Sheet.

    Das Sheet deckt den anderen häufigen Fall ab: ein Filter-Panel, das am Desktop von rechts kommt und auf dem Handy über mobileSide="bottom" zum Bottom Sheet wird.

    Tippe auf Filters: Auf einem breiten Bildschirm gleitet das Panel von rechts herein; unter 768px steigt es von unten auf, und auf einem Touchscreen ziehst du seinen Griff zum Schließen nach unten
    $ npx wingo-ui@latest add sheet
    FreeDoku: Sheet

    Modal vs bottom sheet: Wann sollte ein Handy trotzdem eine zentrierte Karte bekommen?

    Nimm ein Bottom Sheet für Aufgaben und Auswahlen, die man abschließt, um dann zur Seite zurückzukehren, und behalte eine zentrierte Karte für eine kurze Frage, die beantwortet werden muss. Die Human Interface Guidelines von Apple (opens in a new tab) beschreiben ein Sheet als nützlich, um gezielt Informationen abzufragen oder eine einfache Aufgabe zu zeigen, nach der man zur Ausgangsansicht zurückkehrt. Für komplexe oder lange Abläufe raten sie, Alternativen zu Sheets zu prüfen. Unsere Regeln:

    • Formulare, Picker, Filter und Teilen-Optionen: ein Bottom Sheet. Es öffnet sich dort, wo der Daumen schon ist, und schließt mit derselben Bewegung.
    • Destruktive Bestätigungen wie „3 Rechnungen löschen?“: ein zentrierter Alert Dialog. Durch das Wegziehen eines Sheets lässt sich eine Frage, die eine Antwort braucht, zu leicht beiseiteschieben.
    • Lange Editoren und mehrstufige Abläufe: ein Sheet in voller Höhe (size="full") oder eine eigene Seite.

    Weitere Muster dieser Art findest du in der Kategorie Mobile UI in React.

    Was solltest du als Nächstes tun?

    Installier den kostenlosen Dialog mit npx wingo-ui@latest add dialog, öffne deine App bei 390px und zieh ein Sheet zu. Wenn du beim Standard-shadcn/ui bleibst, kopier den Wrapper und den Hook mit useSyncExternalStore von oben in dein Projekt.

    Komponenten in diesem Artikel

    • Dialog

      A React modal dialog for focused tasks: a spring-scaled card on desktop and a bottom sheet on phones, with nesting and scrolling.

      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
    • 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
    • useMediaQuery

      A React useMediaQuery hook that is SSR-safe, plus hooks for phone widths, touch screens and the current Tailwind breakpoint.

      Free

    Häufige Fragen

    Wie mache ich einen shadcn Dialog auf dem Handy responsive?

    Render unter 768px den Drawer und darüber den Dialog, aus einer Root, die den offenen Zustand hält, und mit Teilen, die das passende Drawer- oder Dialog-Stück wählen. Die shadcn-Doku zeigt eine Version mit zwei Zweigen; ein Wrapper mit Context, Credenza, Dice UI oder der Dialog von Wingo UI halten den Inhalt an einer Stelle.

    Was ist der Unterschied zwischen dem shadcn Drawer und dem Sheet?

    Der Drawer ist ein Bottom Sheet, das du zum Schließen ziehen kannst. In der Radix-Version basiert er auf Vaul, in der Base-UI-Version auf Base UI. Das Sheet erweitert den Dialog um ein Panel, das von einer beliebigen Kante hereingleitet; die Radix-Version animiert mit CSS und hat keine Ziehgeste.

    Warum zeigt mein responsiver Dialog kurz die falsche Komponente?

    Viele Media-Query-Hooks, auch der im shadcn-Beispiel, starten mit false und korrigieren sich in einem Effect. Der erste Render jedes Mounts arbeitet also mit einer Vermutung. Lies die Query mit useSyncExternalStore und verzweige nie darauf für etwas, das vor dem Öffnen des Dialogs sichtbar ist.

    Ab welcher Breite sollte ein Dialog zum Bottom Sheet werden?

    768px, der md-Breakpoint von Tailwind, ist die übliche Wahl: Das shadcn-Beispiel, Credenza und Wingo UI schalten alle dort um. Darunter lässt eine zentrierte Karte wenig Platz über der Bildschirmtastatur und setzt ihre Buttons weit weg vom Daumen.

    Ist der Dialog von Wingo UI kostenlos?

    Ja. Dialog, Drawer, Sheet und der Hook useMediaQuery sind kostenlos, und npx wingo-ui@latest add dialog installiert auch den Drawer, den er unter 768px nutzt. Der Alert Dialog, der auf dem Handy eine zentrierte Karte bleibt, gehört zu Pro.

    • Mobile UI
    • Dialog
    • Bottom Sheet
    • shadcn/ui
    • React
    • Next.js

    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
    Next.js und Tailwind CSS

    React Use Mobile Hook: SSR-sicher, ohne Hydration-Fehler

    Warum ein React Use Mobile Hook auf dem Server false liefert, wann CSS-Breakpoints besser passen und wie useSyncExternalStore den Hydration Mismatch verhindert.

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

    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.

    SRSerban Rusu·9. Okt. 2026·11 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
    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