Zum Inhalt springen
    WingoUI
    KomponentenVorlagenDokuPreiseKI-AgentenBlogChangelog
    1. Startseite
    2. Blog
    3. Next.js und Tailwind CSS
    4. React Use Mobile Hook: SSR-sicher, ohne Hydration-Fehler
    Read in English
    1. Blog
    2. Next.js und Tailwind CSS
    3. React Use Mobile Hook: SSR-sicher, ohne Hydration-Fehler
    Next.js und Tailwind CSS
    Next.js und Tailwind CSS

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

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

    Auf dieser Seite

    0%
    1. Warum ist useIsMobile auf dem Server false?
    2. Warum verursacht ein useIsMobile Hook einen Hydration Mismatch?
    3. Wann solltest du CSS-Breakpoints statt eines Hooks nutzen?
    4. Wie schreibst du einen SSR-sicheren useMediaQuery Hook?
      1. Welchen serverFallback solltest du übergeben?
      2. Sollte die Query px oder rem nutzen?
    5. Kann Next.js auf dem Server Mobilgeräte erkennen?
    6. Wie nutzen Sheet und Drawer von Wingo UI den Hook?
    7. Was solltest du als Nächstes tun?
    8. Häufige Fragen
      1. Warum liefert useIsMobile auf dem Server false?
      2. Verursacht der useIsMobile Hook von shadcn einen Hydration Mismatch?
      3. Wie behebe ich einen Hydration Mismatch durch useMediaQuery?
      4. Kann Next.js auf dem Server ein Mobilgerät erkennen?
      5. Warum widerspricht mein useIsMobile Hook dem md-Breakpoint von Tailwind?
      6. Ist der useMediaQuery Hook von Wingo UI kostenlos?

    Kurz gesagt

    Ein React Use Mobile Hook liefert auf dem Server false, weil der Server keinen Bildschirm hat: Er rendert das HTML, bevor irgendein Browser eine Breite meldet, und die Hydration muss diese Annahme wiederholen. Sichtbares Layout schaltest du mit CSS-Breakpoints um, die schon beim ersten Paint greifen. Den Hook behältst du für Verhalten nach einer Interaktion, etwa ein Bottom Sheet statt eines Dialogs. Bau ihn auf useSyncExternalStore mit einem Server-Snapshot, dann passt die Hydration und jeder spätere Mount liest schon beim ersten Render den echten Wert.

    Veröffentlicht am 9. Okt. 2026

    Früher oder später bekommt jede Next.js-App, die auf Smartphones laufen muss, einen React Use Mobile Hook: ein useIsMobile(), das unter 768px true liefert, kopiert aus einem Starter oder aus shadcn/ui. Im Browser funktioniert er, dann überrascht er dich auf drei Arten. Auf dem Server liefert er false, eine unvorsichtige Version wirft einen Hydration-Fehler, und ein Smartphone kann kurz die Desktop-Version von allem zeigen, was der Hook steuert. Dieses Tutorial erklärt, woher jedes dieser Probleme kommt, welche Aufgaben zu CSS-Breakpoints gehören und wie du einen SSR-sicheren Media-Query-Hook auf useSyncExternalStore schreibst, der sauber hydratisiert.

    Warum ist useIsMobile auf dem Server false?

    Weil der Server keinen Bildschirm hat. React rendert das HTML, bevor irgendein Browser eine Breite gemeldet hat. Ein Hook, der matchMedia liest, hat also nichts zu lesen und muss eine feste Annahme zurückgeben. Im Next.js App Router ist die Lücke noch größer: Eine Seite ohne Request-Time-APIs wird einmal beim Build vorgerendert, ganz ohne Request und ohne Besucher.

    Die Annahme selbst schadet nicht. Das Problem ist die Hydration: Der erste Render im Client muss dasselbe Markup erzeugen wie der Server, also wiederholt der Browser die Annahme, und der Wert darf sich erst nach der Hydration ändern. Auf einem Smartphone bleibt die Antwort des Servers vom ersten Paint an stehen, bis das JavaScript geladen, geparst und hydratisiert ist, und auf einer langsamen Verbindung dauert das. Alles Sichtbare, das vom Hook abhängt, ist in diesem ganzen Zeitfenster falsch.

    Die nützliche Frage lautet also: Welche Aufgaben vertragen in diesem Zeitfenster eine Annahme?

    Warum verursacht ein useIsMobile Hook einen Hydration Mismatch?

    Ein useIsMobile Hydration Mismatch entsteht nur, wenn der Hook window während des Renderns liest: Dann widerspricht der erste Render im Browser dem HTML des Servers. Wer nach einem React Is Mobile Hook sucht, findet drei Varianten, und nur die erste tut das. Wir haben jede mit renderToString aus React 19.3 auf dem Server gerendert, sie mit hydrateRoot in Chromium bei 390px über Playwright hydratisiert und 300ms später eine zweite Kopie gemountet, so wie ein Dialog, ein Lazy Panel oder eine clientseitige Route nach der Hydration mountet:

    VarianteHydration bei 390pxErster Render eines späteren Mounts
    typeof window-Check im RenderText: ein Fehler, der Baum wird im Client neu aufgebaut. Eine Klasse: kein Fehler, die Klasse des Servers bleibttrue
    useState + useEffectKein Fehler: false, nach dem Effect truefalse, dann true
    useSyncExternalStore mit Server-SnapshotKein Fehler: false, direkt nach der Hydration truetrue

    Die erste Variante ist die naheliegende:

    tsx
    // falsch: Der Server sagt false, ein Smartphone sagt während der Hydration true
    const isMobile = typeof window !== "undefined" && matchMedia("(max-width: 767.98px)").matches;

    Ändert der Hook Text, meldet React 19 Hydration failed because the server rendered text didn't match the client und baut diesen Baum im Client neu auf. Damit ist die Arbeit des SSR verloren. Die Next.js-Seite zum Hydration-Fehler (opens in a new tab) nennt genau diesen Check als häufige Ursache.

    Wählt der Hook nur einen className, schlägt nichts fehl, und das ist schlimmer. In der Entwicklung loggt React 19 A tree hydrated but some attributes of the server rendered HTML didn't match the client properties. This won't be patched up. Ein Production-Build loggt gar nichts. In unserem Test behielt das Smartphone in beiden Builds nach der Hydration class="desktop", und nichts korrigiert das, solange der Wert gleich bleibt.

    Die zweite Variante ist der shadcn use-mobile Hook, der mit der shadcn/ui Sidebar kommt. Stand Oktober 2026 hält sein Quellcode (opens in a new tab) undefined im State, misst in einem Effect und gibt !!isMobile zurück:

    ts
    const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined)
    React.useEffect(() => {
    const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
    // ...abonniert mql, dann:
    setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
    }, [])
    return !!isMobile

    Er erzeugt nie einen Mismatch, weil jeder erste Render false liefert. Der Haken steckt im Wort „jeder“: Auch eine Komponente, die lange nach der Hydration mountet, rendert einmal als Desktop und korrigiert sich dann im Effect. Ein autoFocus={!isMobile} auf einem Feld in dieser Komponente ist beim ersten Commit true, und React fokussiert das Feld während dieses Commits. Ein Smartphone kann also trotzdem die Tastatur bekommen, die du vermeiden wolltest.

    Die shadcn Sidebar (opens in a new tab) kommt mit dem falschen Start aus gutem Grund durch. Das Desktop-Markup, das sie auf dem Server rendert, trägt hidden md:block, also sieht ein Smartphone es nie, und auf dem Smartphone tauscht die Korrektur ein Sheet ein, das geschlossen bleibt, bis jemand es öffnet. Dieses Muster solltest du kopieren, und der nächste Abschnitt erklärt es im Detail.

    Wann solltest du CSS-Breakpoints statt eines Hooks nutzen?

    Für alles, was vor der ersten Interaktion sichtbar ist. CSS Media Queries greifen beim ersten Paint, bevor irgendein JavaScript läuft, also stimmen sie schon im HTML des Servers. Render beide Versionen und lass einen Breakpoint eine davon ausblenden:

    tsx
    // app/orders/page.tsx: eine Server Component, kein Hook zum Korrigieren
    import { getOrders } from "@/lib/orders";
    import { OrderCard } from "./order-card";
    import { OrderRow } from "./order-row";
    export default async function OrdersPage() {
    const orders = await getOrders();
    return (
    <>
    <ul className="flex flex-col gap-2 md:hidden">
    {orders.map((order) => (
    <OrderCard key={order.id} order={order} />
    ))}
    </ul>
    <table className="hidden w-full md:table">
    <tbody>
    {orders.map((order) => (
    <OrderRow key={order.id} order={order} />
    ))}
    </tbody>
    </table>
    </>
    );
    }

    Beide Bäume stehen im DOM. Für eine Navigation oder eine Seite mit 20 Zeilen kostet das wenig. Hängt der Wechsel vom Platz ab, den eine Komponente bekommt, und nicht vom Fenster, erledigen das die Varianten @container und @md: von Tailwind v4 im CSS, ganz ohne Hook.

    Den Hook behältst du für Entscheidungen, die erst beginnen, wenn jemand etwas tut, oder die beim ersten Paint nie sichtbar sind:

    • welches Overlay sich öffnet: ein Bottom Sheet unter 768px, darüber ein Popover oder ein Dialog
    • von welcher Kante ein Panel hereinfährt
    • ob ein Feld den Fokus bekommt, wenn sich ein Panel öffnet, denn auf dem Smartphone öffnet der Fokus die Tastatur über dem Inhalt
    • wohin ein Klick führt: auf dem Smartphone zu einer eigenen Seite, auf dem Desktop zu einem Seitenpanel neben der Liste

    Ein geschlossenes Overlay rendert nur seinen Trigger, und der Trigger ist in beiden Modi meist derselbe Button. Der Hook kann das Overlay also frei umschalten. Das Tutorial zum responsiven shadcn Dialog baut diesen Wechsel für einen Dialog, und der Guide zu mobile-first React-Komponenten wendet dieselbe Regel auf eine ganze Komponentenbibliothek an.

    Wie schreibst du einen SSR-sicheren useMediaQuery Hook?

    Lies die Query über useSyncExternalStore, die React-API für Werte, die außerhalb von React leben, und gib ihr einen Server-Snapshot. Mehr braucht es nicht, um useMediaQuery SSR-sicher zu machen:

    ts
    // hooks/use-media-query.ts
    import { useCallback, useSyncExternalStore } from "react";
    // das Gegenstück zur md:-Variante von Tailwind, in derselben Einheit
    export const MOBILE_QUERY = "(width < 48rem)";
    const lists = new Map<string, MediaQueryList>();
    function getList(query: string) {
    let list = lists.get(query);
    if (!list) {
    list = window.matchMedia(query);
    lists.set(query, list);
    }
    return list;
    }
    export function useMediaQuery(query: string, serverFallback = false) {
    // stabil pro Query, sonst meldet React das Abo bei jedem Render ab und neu an
    const subscribe = useCallback(
    (onChange: () => void) => {
    const list = getList(query);
    list.addEventListener("change", onChange);
    return () => list.removeEventListener("change", onChange);
    },
    [query],
    );
    return useSyncExternalStore(
    subscribe,
    () => getList(query).matches,
    // der Server und der Hydration-Render
    () => serverFallback,
    );
    }
    export function useIsMobile() {
    return useMediaQuery(MOBILE_QUERY);
    }
    // derselbe Check in Event-Handlern und Effects; auf dem Server false
    export function matchesMedia(query: string) {
    return typeof window !== "undefined" && getList(query).matches;
    }

    Das macht React damit:

    • Server und Hydration. React ruft die dritte Funktion auf, also stimmen das HTML des Servers und der Hydration-Render überein. Lässt du sie weg, sagt die React-Doku (opens in a new tab) unmissverständlich, dass das Rendern der Komponente auf dem Server dann einen Fehler wirft.
    • Direkt nach der Hydration. Sobald die Effects laufen, vergleicht React den Server-Snapshot mit der Antwort des Browsers und rendert synchron neu, wenn sie sich unterscheiden. Das ist das false, dann true aus der Tabelle oben.
    • Jeder spätere Mount. React ruft beim ersten Render die zweite Funktion auf. Ein Sheet, das sich auf dem Smartphone öffnet, rendert also vom ersten Frame an als Smartphone.
    • Updates. Eine MediaQueryList feuert change nur, wenn die Query umschlägt. Das Ziehen am Fensterrand kostet also keine Renders, bis es den Breakpoint überquert, anders als ein resize-Listener. Die Listen werden pro Query gecacht, weil matches bei jedem Render gelesen wird.

    Hat die shadcn Sidebar hooks/use-mobile.ts in dein Projekt gelegt, lass diese Datei auf den neuen Hook zeigen. Die Sidebar behält ihren Import, und ihr Desktop-Markup mit hidden md:block schaltet jetzt bei derselben Breite um wie der Hook:

    ts
    // hooks/use-mobile.ts
    import { MOBILE_QUERY, useMediaQuery } from "@/hooks/use-media-query";
    export const useIsMobile = () => useMediaQuery(MOBILE_QUERY);

    Event-Handler brauchen den Hook gar nicht. Lies die Query im Moment des Klicks:

    tsx
    // app/orders/use-open-order.ts
    "use client";
    import { useRouter } from "next/navigation";
    import { useState } from "react";
    import { MOBILE_QUERY, matchesMedia } from "@/hooks/use-media-query";
    export function useOpenOrder() {
    const router = useRouter();
    const [selected, setSelected] = useState<string | null>(null);
    function openOrder(id: string) {
    // Smartphones bekommen eine eigene Seite, Desktops ein Panel neben der Liste
    if (matchesMedia(MOBILE_QUERY)) router.push(`/orders/${id}`);
    else setSelected(id);
    }
    return { selected, openOrder };
    }

    Welchen serverFallback solltest du übergeben?

    Die Antwort, deren Irrtum im Hydration-Zeitfenster am wenigsten kostet. Für useIsMobile ist false meist richtig, weil die Entscheidungen, die er steuert, erst nach einem Tap beginnen, und bis dahin ist der echte Wert da. Für eine Query, die etwas Sichtbares ändert, wählst du die Antwort, die auf jedem Gerät funktioniert. Bei (hover: hover) ist das false: Der Server rendert die immer sichtbare Version eines Controls, und wer eine Maus nutzt, bekommt den Hover-Feinschliff einen Moment später.

    Sollte die Query px oder rem nutzen?

    Nimm die Einheit deiner CSS-Breakpoints. Tailwind v4 definiert --breakpoint-md: 48rem, und unser Build kompiliert md: zu @media (min-width: 48rem). Laut Media Queries Level 4 (opens in a new tab) beziehen sich relative Einheiten in Media Queries auf den Anfangswert. 48rem folgt also der Standard-Schriftgröße, die im Browser des Besuchers eingestellt ist, und ignoriert dein html { font-size }. Ein Hook in px bewegt sich nicht mit.

    Wir haben das in Chromium über Playwright geprüft, mit einem 900px breiten Fenster und einer Standard-Schriftgröße von 20px. (min-width: 48rem) griff nicht mehr, also zeigte Tailwind das Smartphone-Layout, während (max-width: 767.98px) ebenfalls nicht griff, ein px-Hook meldete also weiter einen Desktop. (width < 48rem) griff, im Gleichschritt mit dem CSS. Die Range-Syntax macht außerdem den .98px-Trick überflüssig, den die Bootstrap-Doku (opens in a new tab) mit Viewports begründet, deren Breite kein ganzzahliger Pixelwert ist. Stand Oktober 2026 führen die Kompatibilitätsdaten von MDN (opens in a new tab) die Range-Syntax ab Chrome 104, Firefox 102 und Safari 16.4.

    Breakpoints sind Tokens wie Farben und Radien, also pflege sie an einer Stelle: Der Guide zu Design Tokens in Tailwind v4 zeigt, wie du sie in Ebenen ordnest. Ein Vorbehalt zu unserem eigenen Code: Stand Oktober 2026 ist die MOBILE_QUERY in Wingo UI noch (max-width: 767.98px) und hat damit die oben beschriebene Lücke. Bei einer Standard-Schriftgröße von 20px schalten md:-Klassen bei 960px um, Drawer und Sheet dagegen, die useIsMobile() aufrufen, bei 768px. Ruf in deinen eigenen Komponenten useMediaQuery("(width < 48rem)") auf, wo Hook und Klassen übereinstimmen müssen.

    Kann Next.js auf dem Server Mobilgeräte erkennen?

    Es kann anhand der Request-Header raten, und zwar nur auf Routen, die bei jedem Request rendern. Wer auf dem Server mit React Mobile erkennen will, liest fast immer den User-Agent-Header. Next.js parst ihn mit userAgent() (opens in a new tab) aus next/server. Dessen device.type lautet "mobile", "tablet" oder einer von ein paar weiteren Werten, bei Desktop-Browsern undefined. Gib die Annahme als Server-Snapshot weiter, dann passt die Hydration trotzdem, weil die Prop mit der Seite mitkommt:

    tsx
    // app/orders/page.tsx
    import { headers } from "next/headers";
    import { userAgent } from "next/server";
    import { OrdersScreen } from "./orders-screen";
    export default async function OrdersPage() {
    const { device } = userAgent({ headers: await headers() });
    return <OrdersScreen phoneGuess={device.type === "mobile"} />;
    }
    tsx
    // app/orders/orders-screen.tsx
    "use client";
    import { MOBILE_QUERY, useMediaQuery } from "@/hooks/use-media-query";
    export function OrdersScreen({ phoneGuess }: { phoneGuess: boolean }) {
    // Server und Hydration-Render nutzen beide die Annahme, dann übernimmt der echte Wert
    const isMobile = useMediaQuery(MOBILE_QUERY, phoneGuess);
    return <p>{isMobile ? "Tippe auf eine Bestellung, um sie zu öffnen" : "Wähle eine Bestellung, um sie rechts zu sehen"}</p>;
    }

    Kenn die Kosten, bevor du das einbaust:

    • Dynamisches Rendering. headers() (opens in a new tab) ist eine Request-Time-API. Die Seite rendert also bei jedem Request statt einmal beim Build, und jeder geteilte Cache muss nach User-Agent unterscheiden. Mit aktivierten Cache Components muss der Aufruf außerdem in einer <Suspense>-Boundary stehen, sonst schlägt next build fehl.
    • Der Header nennt ein Gerät. Er bleibt gleich für einen Desktop-Browser, der auf 600px verkleinert ist, eine Tablet-App im Split Screen und ein Smartphone im Querformat. Die Fensterbreite verrät er dir also nicht. Seit Safari 13 sendet Safari auf dem iPad standardmäßig denselben User-Agent wie Safari auf macOS (opens in a new tab), also gelten die meisten iPads als Desktop.
    • Client Hints gibt es nur in Chromium. Sec-CH-UA-Mobile kommt ohne Opt-in an, aber Stand Oktober 2026 führen die Kompatibilitätsdaten von MDN (opens in a new tab) den Header in Chrome und Edge und nicht in Firefox oder Safari.

    Das lohnt sich auf App-Seiten, die ohnehin dynamisch sind, etwa ein Dashboard nach dem Login, und selten auf Marketing-Seiten, die du aus einem CDN ausliefern willst.

    Wie nutzen Sheet und Drawer von Wingo UI den Hook?

    Nur für Verhalten, und im geschlossenen Zustand rendern sie bei jeder Breite dasselbe Markup. Der kostenlose Hook useMediaQuery ist genauso gebaut, mit demselben Parameter serverFallback, dazu useIsMobile, useIsTouch für (pointer: coarse), useBreakpoint und matchesMedia. Zieh das Fenster über die 768px-Grenze oder öffne die Seite auf einem Smartphone:

    Zieh das Fenster über die 768px-Grenze: Die Karte useIsMobile() und die Karte (width < 48rem) schalten bei der Standard-Schriftgröße gemeinsam um, und das Lineal springt zum breitesten Tailwind-Breakpoint, der greift
    $ npx wingo-ui@latest add use-media-query
    FreeDoku: useMediaQuery

    Der Drawer ruft useIsMobile() in seiner Root-Komponente auf. Smartphones bekommen immer das ziehbare Sheet; ab 768px wählt die Prop desktop ein zentriertes Sheet, ein Seitenpanel oder einen Dialog. Sein Trigger ist in jedem Modus derselbe Radix-Dialog-Trigger, also ändert die Annahme des Servers nichts am Bildschirm. Der Open-State liegt in der Root-Komponente, also bleibt der Drawer offen, wenn jemand ein Tablet über 768px hinweg dreht.

    Tippe auf „Filters“: Unter 768px fährt der Drawer als Sheet hoch, das du nach unten ziehen kannst, um es zu schließen; auf einem breiteren Bildschirm öffnet dieselbe Komponente einen zentrierten Dialog
    $ npx wingo-ui@latest add drawer
    FreeDoku: Drawer

    Das Sheet macht dasselbe für Seitenpanels. mobileSide="bottom" macht aus einem Filter-Panel von rechts auf dem Smartphone ein Bottom Sheet, und die Kante spielt erst eine Rolle, wenn das Sheet offen ist:

    tsx
    import { SlidersHorizontal } from "lucide-react";
    import { Button } from "@/components/ui/button";
    import { Sheet } from "@/components/ui/sheet";
    export function OrderFilters() {
    return (
    <Sheet
    side="right"
    mobileSide="bottom"
    title="Filter"
    description="Status, Datum und Betrag"
    trigger={
    <Button variant="soft" leftIcon={<SlidersHorizontal />}>
    Filter
    </Button>
    }
    >
    <p className="text-sm text-muted-foreground">Wähle die Bestellungen, die du sehen willst.</p>
    </Sheet>
    );
    }

    Was solltest du als Nächstes tun?

    Kopier den Hook von oben nach hooks/use-media-query.ts oder installier unseren mit npx wingo-ui@latest add use-media-query. Dann durchsuch deine Codebase nach useIsMobile und sortier jeden Aufruf: Sichtbares Layout wandert in md:-Klassen, Verhalten bleibt beim Hook. Den anderen Hook, den die meisten Komponenten mit State teilen, erklärt der Guide zum Controllable State Hook in React. Mehr Artikel zum selben Stack findest du unter Next.js und Tailwind CSS.

    Komponenten in diesem Artikel

    • useMediaQuery

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

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

    Häufige Fragen

    Warum liefert useIsMobile auf dem Server false?

    Der Server rendert das HTML, bevor ein Browser eine Bildschirmbreite meldet. Der Hook hat also nichts zu messen und gibt eine feste Annahme zurück. Die Hydration muss diese Annahme wiederholen, der echte Wert kommt direkt danach.

    Verursacht der useIsMobile Hook von shadcn einen Hydration Mismatch?

    Nein. Der use-mobile Hook von shadcn hält undefined im State und misst den Bildschirm in einem Effect, also liefert sein erster Render auf dem Server und im Browser gleichermaßen false. Der Preis: Auch eine Komponente, die erst nach der Hydration mountet, rendert einmal als Desktop, bis der Effect das korrigiert.

    Wie behebe ich einen Hydration Mismatch durch useMediaQuery?

    Lies window nicht mehr während des Renderns, sondern lies die Query über useSyncExternalStore, mit einem getServerSnapshot, der auf dem Server und bei der Hydration denselben Wert liefert. React korrigiert den Wert dann direkt nach der Hydration, ohne Mismatch.

    Kann Next.js auf dem Server ein Mobilgerät erkennen?

    Es kann aus dem User-Agent-Header raten, mit userAgent() aus next/server, und dadurch rendert die Route bei jedem Request. Der Header nennt ein Gerät und sagt nichts über die Breite des Viewports. Nutz die Annahme also nur als Server-Snapshot und lass das Layout im CSS.

    Warum widerspricht mein useIsMobile Hook dem md-Breakpoint von Tailwind?

    Breakpoints in Tailwind v4 sind in rem angegeben (md ist 48rem), und Media Queries rechnen rem mit der Standard-Schriftgröße des Browsers um, während viele Hooks, auch der von shadcn, fest 768px eintragen. Erhöht jemand diese Schriftgröße, gehen beide auseinander. Schreib die Query des Hooks als (width < 48rem), dann schalten beide bei derselben Breite um.

    Ist der useMediaQuery Hook von Wingo UI kostenlos?

    Ja. useMediaQuery, useIsMobile und der Rest der Hook-Datei sind kostenlos, ebenso Sheet und Drawer, die ihn nutzen. Installier den Hook mit npx wingo-ui@latest add use-media-query.

    • React
    • Next.js
    • Hooks
    • SSR
    • Tailwind CSS

    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

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