Zum Inhalt springen
    WingoUI
    KomponentenVorlagenDokuPreiseKI-AgentenBlogChangelog
    1. Startseite
    2. Blog
    3. Mobile UI mit React
    4. Mobile First React: 9 Regeln für Komponenten mit Code
    Read in English
    1. Blog
    2. Mobile UI mit React
    3. Mobile First React: 9 Regeln für Komponenten mit Code
    Mobile UI mit React
    Mobile UI mit React

    Mobile First React: 9 Regeln für Komponenten mit Code

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

    Auf dieser Seite

    0%
    1. Was macht eine React-Komponente mobile-first?
    2. Warum sollte ein Popover auf dem Smartphone ein Bottom Sheet werden?
    3. Wie wechselst du ohne Hydration-Flackern zwischen Sheet und Dialog?
    4. Wie groß sollten Touch-Ziele sein?
    5. Warum gehen Hover-Effekte auf Smartphones kaputt?
    6. Wie hältst du fixierte Leisten von Notch und Home-Indikator fern?
    7. Solltest du auf Mobile 100vh, dvh oder svh nutzen?
    8. Was passiert mit unteren Leisten, wenn die Bildschirmtastatur aufgeht?
    9. Wie sollte eine Datentabelle auf dem Smartphone funktionieren?
    10. Welche Gesten gehören in eine mobile Web-App?
    11. Kann shadcn/ui Mobile?
    12. Welche Komponenten braucht eine React mobile web app?
    13. Wie testest du Mobile-First-React-Komponenten?
    14. Welche Guides vertiefen die einzelnen Regeln?
    15. Wo solltest du anfangen?
    16. Häufige Fragen
      1. Was ist eine Mobile-First-React-Komponente?
      2. Wie groß sollten Buttons in einer mobilen Web-App sein?
      3. Soll ich einen useIsMobile-Hook oder CSS Media Queries nutzen?
      4. Hat shadcn/ui ein Bottom Sheet für Mobile?
      5. Warum funktionieren Hover-Effekte auf Smartphones nicht?
      6. Welche mobilen Komponenten von Wingo UI sind kostenlos?

    Kurz gesagt

    Mobile-First-React-Komponenten sind zuerst für einen Daumen auf einem 390px-Screen gebaut und erst danach für die Maus erweitert. Die neun Regeln: Popover, Selects, Menüs und Dialoge öffnen sich unter 768px als Bottom Sheets; Touch-Ziele haben 44px bei groben Zeigern; kein Control versteckt sich hinter Hover; fixierte Leisten bekommen die Safe-Area-Insets als Padding; volle Höhen in dvh statt vh; untere Leisten verschwinden nicht hinter der Bildschirmtastatur; Tabellenzeilen werden Karten; jede Geste hat eine sichtbare Alternative; und das Layout wechselt über CSS-Breakpoints. Media-Query-Hooks nimmst du nur für Verhalten, denn der Server kennt die Bildschirmbreite nicht.

    Veröffentlicht am 9. Okt. 2026

    Öffne eine typische React-App auf dem Smartphone, und die Schwachstellen zeigen sich immer an denselben Stellen: ein Dropdown-Menü, das über den Bildschirmrand hängt, ein 32px kleiner Icon-Button, den du anvisieren musst, eine Checkout-Leiste hinter dem Home-Indikator, eine Datentabelle, die du seitwärts scrollst, um eine einzige Zeile zu lesen. Mobile First React heißt: Du löst diese Probleme einmal, in der Komponente selbst. Jeder Screen, der sie nutzt, macht es dann richtig, ohne eine Media Query in der Seite. Das hier ist das Regelwerk, dem wir in Wingo UI folgen, mit Begründung und Code für jede Regel. Wir bauen Wingo UI, sind also nicht neutral; Fakten von außen verlinken auf ihre Quellen und tragen ein Datum.

    Was macht eine React-Komponente mobile-first?

    Eine Mobile-First-Komponente ist für einen Daumen auf einem 360 bis 390px breiten Screen mit Bildschirmtastatur entworfen und wird danach für Maus und breite Fenster erweitert. Sie ordnet nicht nur ihr Layout neu an, sie ändert auch ihr Verhalten: welches Overlay sie öffnet, wie groß ihre Trefferfläche ist, was Hover ersetzt und wo sie relativ zu Notch und Tastatur sitzt.

    Die neun Regeln in einer Tabelle, danach ein Abschnitt pro Regel:

    RegelAuf dem SmartphoneWarum
    OverlaysPopover, Selects, Menüs und Dialoge öffnen sich unter 768px als Bottom SheetsEin verankertes Panel hat keinen Platz und liegt unter dem Daumen
    Touch-Ziele44px bei groben Zeigern, 8px AbstandEin Finger ist ungenauer als ein Cursor
    HoverNichts erscheint nur bei HoverTouchscreens haben kein Hover
    Safe AreasFixierte Leisten bekommen env(safe-area-inset-*) als PaddingNotch und Home-Indikator verdecken Inhalt
    Höhedvh oder svh, nie vh100vh ist höher als der sichtbare Screen
    TastaturUntere Leisten verschwinden oder fahren mit nach oben, Inputs haben 16px TextFixierte Leisten landen hinter der Tastatur
    TabellenZeilen werden KartenSeitliches Scrollen versteckt den Großteil jeder Zeile
    GestenJede Geste hat eine sichtbare AlternativeGesten sind unsichtbar, und nicht jeder kann sie ausführen
    Layout und VerhaltenCSS-Breakpoints fürs Layout, ein Hook fürs VerhaltenDer Server kennt die Bildschirmbreite nicht

    Wenn du eine React mobile first UI von Grund auf baust, setz jede Regel in jeder Komponente um, bevor du die nächste angehst: Menschen lernen ein Interface über sein häufigstes Muster.

    Warum sollte ein Popover auf dem Smartphone ein Bottom Sheet werden?

    Ein Popover, das an einem kleinen Trigger hängt, hat auf einem 390px-Screen keinen guten Platz, während ein Bottom Sheet genau dort aufgeht, wo der Daumen schon ist. Der Finger, der den Trigger angetippt hat, verdeckt ihn, das Panel wird abgeschnitten oder über den Trigger geklappt, und ein Tap knapp daneben schließt es. Ein Sheet nimmt die volle Breite ein, hat Platz für 48px hohe Zeilen, lässt sich wegziehen und hält seine Aktionen im unteren Teil des Screens, den ein Daumen erreicht, ohne dass du umgreifst.

    Wir rendern unter 768px standardmäßig ein Bottom Sheet für Popover, Select, Dropdown Menu, Context Menu und Dialog. Die Ausnahmen sind so wichtig wie die Regel:

    • Tooltips werden nie zu Sheets. Auf einem Touchscreen hovert nichts, also steht der Text inline oder hinter einem Long Press.
    • Alert Dialogs bleiben bei jeder Breite zentriert. Eine kurze Frage, die beantwortet werden muss, liest sich wie ein iOS-Alert.
    • Vorschläge unter einem Textfeld bleiben am Feld hängen, weil ein Sheet verdecken würde, was du tippst, wie beim Mention Input. Die Combobox geht den anderen Weg: ein Sheet in voller Höhe mit eigenem Suchfeld.

    In Wingo UI ist der Wechsel ein responsive-Prop, der bei Popover, Select, Dropdown Menu, Dialog und den Date Pickern standardmäßig true ist. Du schreibst die Komponente einmal:

    tsx
    "use client";
    import { ChevronDown } from "lucide-react";
    import { Button } from "@/components/ui/button";
    import { Popover, PopoverClose } from "@/components/ui/popover";
    export function PriceFilter({ onApply }: { onApply: () => void }) {
    return (
    <Popover
    trigger={
    <Button variant="soft" rightIcon={<ChevronDown />}>
    Preis
    </Button>
    }
    title="Preis"
    description="Wähl einen Bereich oder gib deinen eigenen ein."
    footer={
    <PopoverClose asChild>
    <Button size="sm" onClick={onApply}>
    Anwenden
    </Button>
    </PopoverClose>
    }
    >
    <p className="text-muted-foreground">Zwischen 500 $ und 1.500 $</p>
    </Popover>
    );
    }

    Auf dem Desktop ist das ein schwebendes Panel mit einem rechtsbündigen Button zum Anwenden. Unter 768px öffnet dasselbe JSX ein Bottom Sheet mit Griff und einem 48px hohen Button in voller Breite. Gib responsive={false} mit, wenn ein Panel an einem Feld verankert bleiben muss.

    Das Sheet darunter ist der kostenlose Drawer, gebaut auf vaul. Er folgt deinem Finger, schließt sich, wenn du ihn weiter als ein Viertel seiner Höhe herunterziehst und loslässt oder ihn wegschnippst, stoppt an Snap Points und lässt eine lange Liste zuerst scrollen und das Sheet erst ziehen, wenn die Liste wieder oben ist. Beim Öffnen springt der Fokus auf das Sheet selbst statt auf das erste Feld, damit die Tastatur nicht über den halben Inhalt hochfährt. Ab 768px bleibt er ein zentriertes Sheet oder wird je nach desktop-Prop ein Seitenpanel oder ein Dialog.

    Tippe auf Filter: Auf dem Smartphone fährt das Sheet hoch und du kannst es nach unten wegziehen; auf einem breiten Screen öffnet es sich hier als Seitenpanel
    $ npx wingo-ui@latest add drawer
    FreeDoku: Drawer

    Die Demo ist eine Seite mit Inseraten: Filter öffnet unter 768px ein ziehbares Bottom Sheet und auf breiteren Screens ein Panel vom rechten Rand, in beiden Fällen mit der Trefferzahl auf einem fixierten Button.

    Zwei Beiträge gehen bei dieser Regel tiefer. Der Guide zum React Bottom Sheet behandelt Snap Points, Drag to Dismiss, verschachtelte Sheets und die Tastatur, und das Tutorial zum responsiven shadcn-Dialog baut denselben Wechsel von Karte zu Sheet auf Standard-shadcn/ui.

    Wie wechselst du ohne Hydration-Flackern zwischen Sheet und Dialog?

    Nimm CSS-Breakpoints für alles, was beim ersten Paint sichtbar ist, und einen Media-Query-Hook nur für Verhalten, das nach einer Interaktion beginnt, etwa welches Overlay aufgeht. Der Server hat keinen Bildschirm, also muss jeder Hook, der matchMedia liest, beim Server-Rendering und bei der Hydration raten. Steuert diese Vermutung das sichtbare Layout, sehen Smartphones zuerst das Desktop-Layout und dann einen Sprung.

    Der use-mobile-Hook, der mit der Sidebar von shadcn/ui kommt, startet als undefined, gibt false zurück und kippt in einem Effect um (opens in a new tab) (Stand Oktober 2026). Unser kostenloser useMediaQuery nutzt denselben Breakpoint von 768px, liest ihn über useSyncExternalStore und macht den Server-Wert zu einem Parameter. Das ist der Kern:

    ts
    import { useCallback, useSyncExternalStore } from "react";
    export function useMediaQuery(query: string, serverFallback = false) {
    const subscribe = useCallback(
    (onChange: () => void) => {
    const list = window.matchMedia(query);
    list.addEventListener("change", onChange);
    return () => list.removeEventListener("change", onChange);
    },
    [query],
    );
    return useSyncExternalStore(
    subscribe,
    () => window.matchMedia(query).matches,
    // der Server und der Hydration-Render
    () => serverFallback,
    );
    }
    export const useIsMobile = () => useMediaQuery("(max-width: 767.98px)");

    Beide Hooks sagen auf dem Server false. Für Overlays ist das harmlos, denn beim Server-Rendering ist ein Overlay geschlossen: Wenn jemand den Trigger antippt, kennt der Hook längst den echten Wert. Für Inhalte ist es schädlich:

    tsx
    // falsch: der Server rendert die Tabelle, Smartphones wechseln nach der Hydration zu Karten
    const isMobile = useIsMobile();
    return isMobile ? <InvoiceCards rows={rows} /> : <InvoiceTable rows={rows} />;
    // richtig: beides steht im HTML und CSS wählt vor dem ersten Paint
    return (
    <>
    <InvoiceTable rows={rows} className="hidden md:table" />
    <InvoiceCards rows={rows} className="md:hidden" />
    </>
    );

    Genau diesen Teil machen Apps beim Thema mobile first design React falsch, ohne es zu merken, denn die falsche Version sieht gut aus, wenn du ein Desktop-Fenster nach dem Laden schmal ziehst. Der Sprung zeigt sich erst, wenn ein Smartphone die server-gerenderte Seite lädt. Unser Guide zu einem SSR-sicheren React use mobile Hook testet drei verbreitete Versionen des Hooks durch die Hydration und sortiert, welche Aufgaben zu CSS gehören.

    Wie groß sollten Touch-Ziele sein?

    Mach jedes Ziel auf Touchscreens 44 mal 44 CSS-Pixel groß und halte mindestens 8px Abstand zum Nachbarn. Die Standards sind sich bei der genauen Zahl nicht einig; 44px erfüllt das strengste Web-Kriterium (WCAG Stufe AAA) und Apples Standardgröße für Controls:

    Quelle (Stand Oktober 2026)GrößeStatus
    WCAG 2.2, 2.5.8 Target Size (Minimum) (opens in a new tab)24 mal 24 CSS px, oder genug AbstandStufe AA
    WCAG 2.2, 2.5.5 Target Size (Enhanced) (opens in a new tab)44 mal 44 CSS pxStufe AAA
    Apple Human Interface Guidelines (opens in a new tab)44 mal 44 pt Standard auf iOS, 28 mal 28 pt MinimumRichtlinie
    Android-Leitfaden zur Barrierefreiheit (opens in a new tab)48 mal 48 dpEmpfehlung

    Der Haken ist der Desktop. Ein 44px-Button überall lässt eine Toolbar oder eine Einstellungsseite neben einem Mauszeiger aufgebläht wirken. Tailwind CSS v4 hat eine pointer-coarse-Variante (opens in a new tab), die zu @media (pointer: coarse) kompiliert, also kann die Größe dem Eingabegerät folgen statt der Bildschirmbreite. Tablet und Smartphone bekommen beide die größeren Ziele, ein Laptop mit Trackpad nicht.

    Für einen Text-Button lautet die Größe h-10 pointer-coarse:h-11: 40px mit der Maus, 44px unter dem Finger. Ein Icon-Button, der klein bleiben muss, bekommt stattdessen eine unsichtbare Trefferfläche:

    tsx
    import { Ellipsis } from "lucide-react";
    // sieht nach 32px aus, nimmt auf Touch 44px große Taps an
    export function MoreButton() {
    return (
    <button
    type="button"
    aria-label="Weitere Aktionen"
    className="relative grid size-8 touch-manipulation place-items-center [-webkit-tap-highlight-color:transparent] pointer-coarse:after:absolute pointer-coarse:after:-inset-1.5"
    >
    <Ellipsis className="size-4" />
    </button>
    );
    }

    Der ::after-Trick legt auf jeder Seite des 32px-Buttons 6px dazu, ohne etwas zu verschieben. touch-manipulation schaltet den Doppeltipp-Zoom auf dem Control ab, sodass Taps ankommen, ohne dass der Browser auf einen zweiten wartet, und das transparente Tap-Highlight entfernt den grauen Kasten, den mobile Browser über ein angetipptes Element malen. Beides sitzt in der Komponente selbst, denn eine Komponente, die du in ein anderes Projekt kopierst, bringt dein globales Stylesheet nicht mit. Der Button in Wingo UI ist so gebaut: md hat 40px mit der Maus und 44px auf Touch, sm und icon-sm bleiben 32px und bekommen die unsichtbare Trefferfläche, und lg hat überall 48px. Der Guide zur minimalen Touch-Target-Größe vergleicht, was WCAG, Apple und Android verlangen, und zeigt, wie du die Trefferflächen testest.

    Warum gehen Hover-Effekte auf Smartphones kaputt?

    Touchscreens haben kein Hover, also existiert alles, was nur bei Hover erscheint, auf dem Smartphone nicht. In Tailwind CSS v4 greift die hover:-Variante nur, wenn das primäre Eingabegerät hovern kann (opens in a new tab) (@media (hover: hover)). Das behebt den alten Hover-Zustand, der nach einem Tap hängen blieb, und es bedeutet auch, dass ein reines Hover-Control auf Touch überhaupt nie auftaucht.

    Der klassische Fall ist eine Zeilenaktion, die einblendet, wenn der Zeiger über der Zeile ist. Die Lösung ist eine Fokus-Variante plus eine Variante für grobe Zeiger:

    tsx
    import { Ellipsis } from "lucide-react";
    export function ClientRow({ name }: { name: string }) {
    return (
    <li className="group/row flex h-12 items-center justify-between gap-3 px-4">
    {name}
    <button
    type="button"
    aria-label={`Aktionen für ${name}`}
    className="grid size-8 place-items-center opacity-0 transition-opacity group-hover/row:opacity-100 group-focus-within/row:opacity-100 pointer-coarse:opacity-100"
    >
    <Ellipsis className="size-4" />
    </button>
    </li>
    );
    }

    Hover blendet ihn für die Maus ein, Fokus für die Tastatur, und bei einem groben Zeiger ist er immer da. Die Data Table setzt dieselben Klassen auf den Wrapper um den Trigger ihres Zeilenmenüs, dazu has-[[data-state=open]]:opacity-100: Ohne das blendet der Trigger aus, während sein Menü offen ist, sobald der Zeiger von der Zeile ins Menü wandert. Die beiden anderen Ersatzformen für Hover sind Tap und Long Press: Eine Hover Card öffnet sich beim ersten Tap als Sheet, ein Tooltip erscheint bei einem Long Press, und ein Context Menu öffnet sich als Action Sheet, wenn du eine Zeile gedrückt hältst.

    Wie hältst du fixierte Leisten von Notch und Home-Indikator fern?

    Setz viewport-fit=cover und gib dann jedem Element, das an einem Bildschirmrand klebt, env(safe-area-inset-*) als Padding, mit einem Minimum für Geräte, die null melden. cover skaliert den Viewport auf das ganze Display, Notch und Home-Indikator inklusive, und MDN empfiehlt dazu dringend (opens in a new tab) die Safe-Area-Inset-Variablen, damit wichtiger Inhalt sichtbar bleibt.

    Im Next.js App Router kommt das Viewport-Meta-Tag aus einem viewport-Export im Root-Layout:

    tsx
    // app/layout.tsx
    import type { Viewport } from "next";
    export const viewport: Viewport = {
    width: "device-width",
    initialScale: 1,
    viewportFit: "cover",
    };

    Dann paddest du mit einem Arbitrary Value: pb-[max(env(safe-area-inset-bottom),12px)]. Ein einfaches pb-[env(safe-area-inset-bottom)] lässt Buttons auf Geräten ohne Home-Indikator, wo der Inset 0 ist, direkt am Bildschirmrand kleben; max() lässt etwas Luft.

    Schwieriger wird es mit zwei Leisten gleichzeitig. Eine Produktseite auf dem Smartphone hat oft unten eine Tab Bar und darüber eine Kaufleiste, und beide wollen die Safe Area. Die Bottom Tab Bar lässt Platz für den Home-Indikator und schreibt ihre Höhe in eine CSS-Variable --bottom-bar-offset. Die Sticky CTA Bar sitzt bei bottom: var(--bottom-bar-offset) und paddet nur den Teil der Safe Area, den die Tab Bar nicht schon abdeckt, also gibt es keine doppelte Lücke:

    tsx
    "use client";
    import { House, Search, ShoppingBag, UserRound } from "lucide-react";
    import { BottomTabBar, type BottomTabBarItem } from "@/components/ui/bottom-tab-bar";
    import { StickyCtaBar } from "@/components/ui/sticky-cta-bar";
    const tabs: BottomTabBarItem[] = [
    { label: "Start", href: "/", icon: <House /> },
    { label: "Suche", href: "/search", icon: <Search /> },
    { label: "Warenkorb", href: "/cart", icon: <ShoppingBag />, badge: 2 },
    { label: "Konto", href: "/account", icon: <UserRound /> },
    ];
    export function ProductChrome({ onAddToCart }: { onAddToCart: () => Promise<void> }) {
    return (
    <>
    <StickyCtaBar
    price={349}
    compareAtPrice={449}
    locale="en-US"
    actionLabel="In den Warenkorb"
    tone="brand"
    hideFrom="md"
    onAction={onAddToCart}
    />
    <BottomTabBar items={tabs} className="md:hidden" hideOnScroll />
    </>
    );
    }

    Die Tab Bar nimmt 3 bis 5 Tabs, jeder weit größer als 44px. Mit hideOnScroll folgt sie deinem Finger auf dem Weg nach unten aus dem Bild und kommt auf dem Weg nach oben zurück, bewegt von einem Motion Value, sodass pro Frame nichts neu rendert. Die Kaufleiste zeigt einen Spinner, solange das Promise von onAction läuft, ohne ihre Breite zu ändern. Das Tutorial zu Tailwind Safe Area Insets behandelt die Utilities, einen FAB über der Tab Bar und wohin Toasts gehören, und der Guide zur React Bottom Navigation Bar behandelt Badges, die mittlere Aktion und wann eine Sidebar besser passt.

    Tippe auf die Tabs, dann noch einmal auf den aktiven, um nach oben zurückzuspringen; der erhöhte Add-Button öffnet ein Sheet
    $ npx wingo-ui@latest add bottom-tab-bar
    ProDoku: Bottom Tab Bar

    Die Demo ist ein kleines CRM mit vier Tabs, einem Zähler für Ungelesenes auf Inbox und einer erhöhten Add-Aktion. Öffne einen Kunden und tippe dann wieder auf Clients: Der Tab springt zurück auf seinen ersten Screen, so wie Tab Bars auf iOS.

    Solltest du auf Mobile 100vh, dvh oder svh nutzen?

    Nimm dvh für App Shells und Sheets, svh für Blöcke, die beim Scrollen ihre Höhe nicht ändern dürfen, und nie vh. MDN definiert (opens in a new tab) vh als identisch mit lvh, also den großen Viewport, gemessen mit eingefahrenen Browserleisten. Solange die Adressleiste sichtbar ist, ist 100vh (Tailwinds h-screen) höher als der sichtbare Bereich, und das untere Ende deines Layouts, oft genau der wichtige Button, liegt unter der Toolbar.

    dvh folgt den Leisten, wenn sie ein- und ausfahren, und das will eine App Shell in voller Höhe. MDN warnt aber auch, dass dynamische Einheiten Inhalte während des Scrollens in der Größe ändern können, also kann eine Hero-Section in dvh zucken; dort ist svh die stabile Wahl. Tailwind CSS v4 liefert h-dvh, min-h-dvh, h-svh und h-lvh. In Wingo UI nutzt die App Shell min-h-dvh, und Bottom Sheets sind auf max-h-[92dvh] begrenzt, damit ein hohes Sheet über sich immer einen Streifen der Seite sichtbar lässt, egal was die Leisten tun. Der 100vh-Fix für Mobile vergleicht die drei Einheiten im Detail und baut beide Arten von App Shell in voller Höhe.

    Was passiert mit unteren Leisten, wenn die Bildschirmtastatur aufgeht?

    Sie landen hinter der Tastatur, wenn du sie nicht bewegst. Seit Chrome 108 auf Android (opens in a new tab) verkleinert die Tastatur nur den Visual Viewport; das Chrome-Team beschreibt das als Angleichung an Safari auf iOS. Ein Element mit position: fixed; bottom: 0 richtet sich am Layout Viewport aus, und der reicht weiterhin bis zum unteren Bildschirmrand, hinter die Tastatur.

    Es gibt zwei gute Antworten. Blende die Leiste aus, solange ein Feld den Fokus hat; das passt zu einer Checkout-Leiste mit Gutscheinfeld (der Standard der Sticky CTA Bar, keyboard="hide"). Oder heb sie um die Höhe an, die die Tastatur verdeckt; das passt zu einem Chat-Composer (keyboard="lift"). Die Anhebung kommt aus dem Visual Viewport: Die verdeckte Höhe ist innerHeight - visualViewport.height - visualViewport.offsetTop, solange ein Textfeld den Fokus hat. Der Hook useKeyboardInset (Pro) misst sie höchstens einmal pro Frame und gibt sie als Motion Value heraus, sodass sich die Leiste ohne Re-Render bewegt:

    tsx
    "use client";
    import { motion, useTransform } from "motion/react";
    import { useKeyboardInset } from "@/hooks/use-keyboard-inset";
    export function Composer() {
    const { inset } = useKeyboardInset();
    const y = useTransform(inset, (value) => -value);
    return (
    <motion.form style={{ y }} className="fixed inset-x-0 bottom-0 bg-background px-4 pt-2 pb-[max(env(safe-area-inset-bottom),12px)]">
    <input
    aria-label="Nachricht"
    inputMode="text"
    enterKeyHint="send"
    autoComplete="off"
    className="h-11 w-full rounded-full bg-foreground/5 px-4 text-base md:text-sm"
    />
    </motion.form>
    );
    }

    Zwei weitere Tastaturregeln. Gib Texteingaben auf Smartphones 16px Text (text-base md:text-sm): iOS Safari zoomt die Seite in jedes fokussierte Feld mit kleinerem Text und zoomt nicht wieder heraus. Und setz inputMode, enterKeyHint und autoComplete auf jedes Feld, damit das Smartphone die richtige Tastatur, die richtige Beschriftung der Eingabetaste und das richtige Autofill zeigt.

    Wie sollte eine Datentabelle auf dem Smartphone funktionieren?

    Unter 768px wird jede Zeile zur Karte: das wichtigste Feld als Titel, ein Status-Badge oder ein Betrag in der Ecke, zwei bis vier weitere Felder als Paare aus Label und Wert, und die Zeilenaktionen in einem Menü, das als Sheet aufgeht. Horizontales Scrollen ist nur in Ordnung, wenn es ums Vergleichen von Spalten geht, wie in einer Tabellenkalkulation oder einer Preismatrix. Bei einer Liste von Rechnungen oder Kunden versteckt es den Großteil jeder Zeile und zwingt Leute, in zwei Richtungen zu scrollen, um einen Datensatz zu lesen.

    Die Data Table macht das standardmäßig (mobileLayout="cards"). Jede Spalte kann mit einer mobile-Rolle sagen, wo sie auf der Karte landet, und wenn du das nicht tust, rät die Tabelle sinnvoll (die erste Spalte ist der Titel, das erste Badge oder sonst die letzte Zahl ist die Meta-Angabe):

    tsx
    "use client";
    import { Pencil } from "lucide-react";
    import { DataTable, createColumns } from "@/components/ui/data-table";
    type Invoice = { id: string; client: string; city: string; status: string; amount: number };
    const col = createColumns<Invoice>();
    const columns = col([
    col.accessor("client", { header: "Kunde", mobile: "title" }),
    col.accessor("city", { header: "Stadt", mobile: "subtitle" }),
    col.accessor("status", {
    header: "Status",
    type: "badge",
    badge: { Paid: { tone: "success" }, Overdue: { tone: "danger" }, Draft: { tone: "neutral" } },
    mobile: "meta",
    }),
    col.accessor("amount", { header: "Betrag", type: "currency" }),
    col.accessor("id", { header: "Rechnung", mobile: "hidden" }),
    ]);
    export function Invoices({ invoices }: { invoices: Invoice[] }) {
    return (
    <DataTable
    data={invoices}
    columns={columns}
    caption="Rechnungen"
    locale="en-US"
    selectionMode="multiple"
    rowActions={(row) => [{ label: "Bearbeiten", icon: <Pencil />, onSelect: () => console.log(row.id) }]}
    />
    );
    }

    Auf dem Smartphone startest du die Auswahl mit einem Long Press auf eine Karte, tippst weitere an, und die Sammelaktionen schweben unten. Sortierung, Filter und das Zeilenmenü öffnen sich als Bottom Sheets, und statt eines Pagers mit winzigen Seitenzahlen hängt „Load more“ weitere Zeilen an. Tabelle und Karten stehen beide im Server-HTML und werden über Breakpoints ein- und ausgeblendet, also sieht ein Smartphone nie zuerst die Tabelle. Das Tutorial zur TanStack Table v9 Data Table baut Filter, Sammelaktionen und Server-Pagination direkt auf TanStack, samt Smartphone-Layout.

    Auf dem Smartphone ist jede Zeile eine Karte, die du per Long Press auswählst; auf einem breiten Screen bekommst du die volle Tabelle mit Sortierung und Spaltenmenüs
    $ npx wingo-ui@latest add data-table
    ProDoku: Data Table

    Die Demo ist eine Kundenliste mit Suche, Schnellfiltern, Status-Badges und Salden. Unter 768px wird jede Zeile zu einer Karte mit dem Kundennamen als Titel und dem Status in der Ecke. Welche Spalten es auf eine Karte schaffen und wie Sortierung und Sammelaktionen ohne Spaltenköpfe funktionieren, steht im Guide zu responsiven Tabellen auf Mobile.

    Welche Gesten gehören in eine mobile Web-App?

    Drag to Dismiss, Pull to Refresh, Swipe-Aktionen auf Zeilen und Halten zum Bestätigen gehören alle dazu, solange jede eine sichtbare Alternative hat und das native Scrollen in Ruhe lässt. Gesten sind unsichtbar, bis sie dir jemand zeigt, und wer eine Tastatur, eine Schaltersteuerung oder einen Screenreader nutzt, kann sie gar nicht ausführen.

    • Immer ein Weg per Button. Sheets schließen mit Escape, einem Tap daneben oder einem Schließen-Button. Pull to Refresh kommt zusammen mit einem Button zum Aktualisieren. Swipe-Aktionen einer Zeile stehen auch in ihrem Menü.
    • touch-action auf ziehbaren Flächen. touch-pan-y auf etwas, das du seitwärts wischst, hält das vertikale Scrollen nativ; touch-none ist für Flächen, die du frei ziehst, etwa ein Unterschriftenfeld oder den Thumb eines Sliders.
    • overscroll-behavior: contain auf Scroll-Containern. Es verhindert, dass ein Scroll in einem Sheet auf die Seite überspringt, und hält das browsereigene Pull to Refresh davon ab, gegen deins zu kämpfen.
    • Motion Values statt State. Ein Drag, der bei jeder Zeigerbewegung setState aufruft, rendert in jedem Frame neu. Schreib den Versatz in einen Motion Value und aktualisiere den State erst, wenn die Geste endet.

    Sortierbare Listen und Boards sind die Stelle, an der diese Regeln mit dem nativen Scrollen kollidieren; das Tutorial zu dnd kit auf Mobile richtet Touch-Sensoren ein, bei denen ein Wischen die Seite trotzdem scrollt.

    Pull To Refresh zeigt, wo sich die Regeln biegen. Die Komponente setzt overscroll-behavior-y: contain auf ihren Scroller und bewegt den Inhalt mit einem Motion Value unter Gummiband-Widerstand. touch-action kann sie nicht nutzen, weil die Liste darin nativ weiterscrollen muss; also hört sie auf touchmove mit passive: false und bricht den Scroll nur bei einem Zug nach unten ganz oben ab. Sie tickt, sobald der Zug die Auslöseschwelle erreicht, dreht sich, bis dein Promise erledigt ist (mindestens 500ms, damit nichts aufblitzt), und bietet dasselbe Aktualisieren als API für einen Button an (der Guide zu React Pull to Refresh behandelt die Indikator-Stile, Barrierefreiheit und lange virtualisierte Listen):

    tsx
    "use client";
    import { useState, type ReactNode } from "react";
    import { RotateCw } from "lucide-react";
    import { Button } from "@/components/ui/button";
    import { PullToRefresh, type PullToRefreshApi } from "@/components/ui/pull-to-refresh";
    export function Feed({ reload, children }: { reload: () => Promise<void>; children: ReactNode }) {
    const [api, setApi] = useState<PullToRefreshApi>();
    return (
    <div className="flex h-dvh flex-col">
    <header className="flex h-14 items-center justify-end px-4">
    <Button variant="ghost" size="icon" radius="full" aria-label="Neu laden" onClick={() => api?.refresh()}>
    <RotateCw />
    </Button>
    </header>
    <PullToRefresh scroll="self" className="min-h-0 flex-1" setApi={setApi} onRefresh={reload}>
    {children}
    </PullToRefresh>
    </div>
    );
    }

    Halten zum Bestätigen ist die Geste für Aktionen, die mehr als einen Tap und weniger als einen Dialog verdienen, etwa einen Kunden zu löschen. Der Haptic Button füllt sich in mode="hold", während du einen Finger, die Maustaste, Space oder Enter hältst, und führt onConfirm aus, wenn die Füllung voll ist. Screenreader und Sprachsteuerung, die mit einem einfachen Klick auslösen und nicht halten können, bekommen stattdessen einen Weg in zwei Schritten: Die erste Aktivierung kündigt „Activate again to confirm“ an, und eine zweite innerhalb von 4 Sekunden bestätigt.

    tsx
    "use client";
    import { Trash2 } from "lucide-react";
    import { HapticButton } from "@/components/ui/haptic-button";
    export function DeleteClient({ onDelete }: { onDelete: () => Promise<void> }) {
    return (
    <HapticButton tone="danger" variant="soft" mode="hold" fullWidth leftIcon={<Trash2 />} onConfirm={onDelete}>
    Halten zum Löschen
    </HapticButton>
    );
    }

    Auf Android nutzt der Tick navigator.vibrate. Safari auf dem iPhone hat keine Vibration API, also weicht der Haptik-Helper auf einen Klick auf einen versteckten nativen Switch aus (<input type="checkbox" switch>). Das hat bis iOS 26.4 den System-Tick ausgelöst; ab iOS 26.5 tickt WebKit nur noch, wenn die Person selbst auf den Switch tippt, also trägt auf einem aktuellen iPhone die Füllung das Feedback allein. Desktops und Mausklicks bleiben still. Der Guide zur Web Vibration API zeigt, was auf einem iPhone noch tickt und wie Leute Haptik abschalten können.

    Kann shadcn/ui Mobile?

    Teilweise. Stand Oktober 2026 beschränkt sich shadcn mobile auf eine Komponente und ein Muster. Die Drawer-Doku (opens in a new tab) gibt es in einer Base-UI-Version, die inzwischen den Drawer von Base UI statt Vaul nutzt, und in einer Radix-Version, die weiterhin auf Vaul aufbaut (opens in a new tab). Beide haben einen Abschnitt „Responsive“, der auf dem Desktop einen Dialog und auf dem Smartphone einen Drawer rendert; das Beispiel entscheidet mit useMediaQuery("(min-width: 768px)") und teilt ein Formular zwischen beiden Zweigen. Die Popover-Doku (opens in a new tab) beschreibt kein mobiles Verhalten, und Select und Dropdown Menu bleiben bei jeder Breite schwebende Panels, solange du nicht für jedes einen Wrapper schreibst.

    Das passt zu einer Bibliothek, die als minimaler Ausgangspunkt ohne feste Vorgaben gedacht ist. shadcn/ui ist kostenlos und MIT-lizenziert, sein GitHub-Repository zeigt rund 125k Sterne (Stand Oktober 2026), und in der Doku wählst du zwischen Primitives von Base UI, Radix und React Aria. Wenn deine App vor allem auf Desktops läuft oder du jeden responsiven Wrapper selbst schreiben und besitzen willst, ist es die bessere Wahl. Unser Vergleich der besten React-Komponentenbibliotheken für Mobile prüft shadcn/ui und acht weitere bei 390px.

    Wenn die meisten deiner Nutzer über das Smartphone kommen, summieren sich die Wrapper: Jedes Overlay braucht den Wechsel, jedes Control die Größe für grobe Zeiger, jede fixierte Leiste die Safe Area, und jedes davon muss das Hydration-Flackern vermeiden. Wingo UI trifft diese Entscheidungen in jeder Komponente, also sind die Standards schon mobile-first, und du schaltest sie pro Instanz ab.

    Welche Komponenten braucht eine React mobile web app?

    Eine React mobile web app braucht meist Overlays, die zu Sheets werden, eine Tab Bar für die Hauptnavigation, eine Aktionsleiste unten auf Produkt- und Checkout-Seiten, Listen mit Swipe-Aktionen und eine Tabelle, die zu Karten wird. Hier ist das Set in Wingo UI (Registry 1.7.0, 326 Einträge, Stand Oktober 2026), mit dem, was jede Komponente auf dem Smartphone tut:

    KomponenteAufgabe auf dem SmartphoneZugang
    DrawerDas Bottom Sheet: Drag to Dismiss, Snap Points, VerschachtelungKostenlos
    Popover, Select, Dropdown Menu, DialogÖffnen sich unter 768px als Bottom SheetsKostenlos
    Segmented ControlWenige Optionen auf einem Thumb, den du ziehen kannstKostenlos
    Bottom Tab Bar3 bis 5 Hauptziele am DaumenPro
    Sticky CTA BarPreis und Hauptaktion, über der Tab BarPro
    Pull To RefreshEine Liste per Zug oder Button neu ladenPro
    ListGruppierte Zeilen mit Swipe-Aktionen und Drag zum UmsortierenPro
    Data TableZeilen als Karten, Long Press zum AuswählenPro
    Haptic ButtonDruck-Feedback, Haptik und Halten zum BestätigenPro
    Large Title HeaderEin großer iOS-Titel, der zu einer Leiste zusammenklapptPro
    Action SheetEine kurze Liste von Aktionen zu einer SachePro
    Picker WheeliOS-Räder für Uhrzeiten und DatenPro

    Die ganze Kategorie Mobile umfasst 15 Komponenten, alle nach diesen Regeln gebaut, mit Unterstützung für reduzierte Bewegung und gestalteten Light- und Dark-Themes.

    Wie testest du Mobile-First-React-Komponenten?

    Teste bei 390 mal 844 und 360 mal 780 mit aktiviertem Touch und dann auf einem echten iPhone für die Teile, die Emulatoren falsch machen. Der Device Mode von Chrome stellt den Zeiger auf grob, das reicht, um pointer-coarse-Styles und die Sheets zu sehen. Die iOS-Tastatur, den Fokus-Zoom, den Home-Indikator und das Ein- und Ausfahren der Toolbar bildet er nicht nach, dafür brauchst du ein Smartphone.

    Für automatische Checks kann Playwright pro Testdatei ein Touch-Smartphone emulieren:

    ts
    import { expect, test } from "@playwright/test";
    test.use({ viewport: { width: 390, height: 844 }, hasTouch: true, isMobile: true });
    test("Filter öffnen auf dem Smartphone als Bottom Sheet", async ({ page }) => {
    await page.goto("/listings");
    await page.getByRole("button", { name: "Filter" }).tap();
    await expect(page.getByRole("dialog", { name: "Filter" })).toBeVisible();
    });

    Mach denselben Durchgang in Light und Dark und mit eingeschalteter reduzierter Bewegung. Ein Sheet-Griff oder eine Haarlinie mit geringer Deckkraft kann in einem Theme klar sein und im anderen verschwinden, und bei reduzierter Bewegung muss jedes Overlay trotzdem auf- und zugehen, nur mit einem kürzeren Slide oder einer Überblendung.

    Welche Guides vertiefen die einzelnen Regeln?

    Jeder dieser Beiträge in Mobile UI in React führt eine Regel oder eine Komponente weiter:

    • Responsiver shadcn-Dialog: eine zentrierte Karte auf dem Desktop und ein Bottom Sheet auf Smartphones, mit einmal geschriebenem Inhalt.
    • React Bottom Sheet fürs Web: Snap Points, Drag to Dismiss, verschachtelte Sheets und die Tastatur auf vaul.
    • Minimale Touch-Target-Größe: die Zahlen von WCAG, iOS und Android und 44px auf Touch in Tailwind ohne aufgeblähten Desktop.
    • Tailwind Safe Area Insets: Leisten unten, FABs und Toasts frei von Notch und Home-Indikator.
    • 100vh auf Mobile: wann dvh, svh oder lvh, und App Shells in voller Höhe, die nicht springen.
    • React Bottom Navigation Bar: eine Tab Bar mit Badges, mittlerer Aktion und Ausblenden beim Scrollen.
    • React Pull to Refresh: ein eigenes Aktualisieren in einer PWA, das nicht gegen das des Browsers kämpft.
    • Web Vibration API: das haptische Feedback, das eine Seite auf Android und iPhone auslösen kann.
    • React PWA Install Prompt: ein Installationshinweis für Chrome, Edge und iPhone, und wann du besser nicht fragst.
    • Responsive Tabellen auf Mobile: wann Zeilen zu Karten werden, welche Spalten überleben, und Sortierung und Sammelaktionen ohne Spaltenköpfe.
    • React Segmented Control: der iOS-Thumb, den du ziehen kannst, Radio-Semantik, und wann Tabs oder eine Toggle Group besser passen.
    • React Wheel Picker: iOS-Räder für Uhrzeiten und Daten auf einem nativ einrastenden Scroller, mit einem Eingabefeld auf dem Desktop.
    • React Swipe to Delete: iOS-Swipe-Aktionen auf Listenzeilen, ein voller Swipe, der löscht, Rückgängig in einem Toast und ein Menü für alle, die nicht wischen können.
    • React Slide to Confirm: wann ein Slider bei unumkehrbaren Aktionen einen Bestätigungsdialog schlägt, mit Tastaturweg und Statusansagen.
    • React Swipe Cards: ein Kartenstapel im Tinder-Stil mit Neigung, Stempeln, Rückgängig und Buttons auf Motion Values.
    • Tailwind Touch Manipulation: die touch-action-Utility, die jeder Button, Slider, jede Swipe-Zeile und jedes Sheet braucht.
    • iOS Safari, Tastatur und position fixed: die Tastatur mit visualViewport messen und einen Composer oder ein Sheet darüber halten.
    • Eine Web-App, die sich nativ anfühlt: 12 Details, von sofortigen Taps bis zu Action Sheets, großen Titeln und Haptik, mit denen sich eine React-Web-App auf dem Smartphone zu Hause fühlt.
    • React Action Sheet: eine Liste von Aktionen im iOS-Stil mit separatem Abbrechen, asynchronen Zeilen, die einen Spinner zeigen und sich erneut auslösen lassen, und einem kompakten Menü auf dem Desktop.
    • React Floating Action Button: eine Hauptaktion in der Ecke, ein Speed Dial, ein Label, das beim Scrollen einklappt, und ein Abstand über Tab Bars und dem Home-Indikator.
    • React Swipeable Tabs: Tab-Seiten auf einer CSS-Scroll-Snap-Spur, sodass ein seitliches Wischen den Tab wechselt und ein vertikaler Scroll nie.
    • React Collapsing Header: der große iOS-Titel und ein Foto-Hero, gesteuert von einem Motion Value, ohne React-Render pro Scroll-Event.

    Wo solltest du anfangen?

    Fang mit den Overlays an, denn sie sind der sichtbarste Fehler auf Mobile und am günstigsten zu beheben. Installier das kostenlose Popover mit npx wingo-ui@latest add popover (es bringt Drawer, Button und den Media-Query-Hook mit), setz es bei einem Panel ein, das Leute auf dem Smartphone öffnen, und probier es auf deinem eigenen Gerät aus. Tab Bar, Sticky CTA Bar, Pull to Refresh, Haptic Button und Data Table gehören zu Wingo UI Pro.

    Komponenten in diesem Artikel

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

      A React popover for rich content anchored to a trigger, like a quick filter, form or share panel, that becomes a bottom sheet on phones.

      Free
    • Bottom Tab Bar

      A React bottom tab bar for phones: 3 to 5 tabs at the thumb with an animated pill, badges, a raised center action and hide on scroll.

      Pro
    • Pull To Refresh

      A React pull to refresh wrapper: pull a list past its top to reload it, with rubberband resistance, iOS spokes or a Material ring, haptics.

      Pro
    • Sticky CTA Bar

      A React sticky CTA bar for product, listing and checkout pages on phones: the price on one side, the main action on the other.

      Pro
    • Data Table

      A React data table on TanStack Table: search, filters, sorting, bulk actions, pinning, resizing, virtualization and cards on phones.

      Pro
    • Haptic Button

      A React haptic button that feels native: spring press, touch ripple, a haptic tick, a long-press action and a hold-to-confirm mode.

      Pro

    Häufige Fragen

    Was ist eine Mobile-First-React-Komponente?

    Eine Komponente, die zuerst für Touch, einen schmalen Screen und eine Bildschirmtastatur entworfen ist und dann für Maus und breite Fenster erweitert wird. Auf dem Smartphone ändert sie nicht nur ihr Layout, sondern auch ihr Verhalten: Sie öffnet ein Bottom Sheet statt eines Popovers, behält 44px große Trefferflächen und hat keine Controls, die nur bei Hover erscheinen.

    Wie groß sollten Buttons in einer mobilen Web-App sein?

    WCAG 2.2 verlangt 24 mal 24 CSS-Pixel auf Stufe AA und 44 mal 44 auf Stufe AAA. Apples Richtlinien nutzen 44 mal 44 Punkt als Standardgröße für iOS-Controls, Android empfiehlt 48 mal 48 dp. Eine praktische Regel fürs Web: 44px bei groben Zeigern und 8px Abstand zwischen den Zielen.

    Soll ich einen useIsMobile-Hook oder CSS Media Queries nutzen?

    Nimm CSS-Breakpoints für alles, was beim ersten Paint sichtbar ist, denn der Server kennt die Bildschirmbreite nicht und ein Hook liefert dort nur eine Vermutung. Den Hook nimmst du für Verhalten, das erst nach einer Interaktion beginnt, etwa ein Bottom Sheet statt eines Popovers zu öffnen.

    Hat shadcn/ui ein Bottom Sheet für Mobile?

    Ja, den Drawer. Stand Oktober 2026 baut seine Base-UI-Version auf dem Drawer von Base UI auf und seine Radix-Version auf Vaul, und die Doku zeigt ein responsives Beispiel, das auf dem Desktop einen Dialog und auf dem Smartphone einen Drawer rendert. Popover, Select und Dropdown Menu wechseln nicht von selbst zu einem Sheet.

    Warum funktionieren Hover-Effekte auf Smartphones nicht?

    Touchscreens haben kein Hover, und in Tailwind CSS v4 greift die hover-Variante nur, wenn das primäre Eingabegerät hovern kann. Ein Control, das nur bei Hover erscheint, taucht auf dem Smartphone also nie auf. Zeig es auch bei Fokus und bei groben Zeigern, oder ersetz das Hover durch einen Tap oder einen Long Press.

    Welche mobilen Komponenten von Wingo UI sind kostenlos?

    Drawer, Sheet, Popover, Dialog, Select, Dropdown Menu, Segmented Control, Button und der Hook useMediaQuery sind kostenlos, und Popover, Dialog, Select und Dropdown Menu öffnen sich unter 768px standardmäßig als Bottom Sheets. Die 15 Komponenten der Kategorie Mobile (Tab Bar, Sticky CTA Bar, Pull to Refresh, Haptic Button und mehr) und die Data Table gehören zu Pro.

    • Mobile UI
    • React
    • Tailwind CSS
    • Bottom Sheet
    • Touch-Ziele
    • Barrierefreiheit

    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
    Vergleiche und Alternativen

    Die beste React Mobile UI Library 2026: 9 im Vergleich

    Welche React Mobile UI Library passt 2026? 9 Bibliotheken bei 390px geprüft: Bottom Sheets, Touch-Ziele, Safe Areas und Gesten, mit klaren Empfehlungen.

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

    React Floating Action Button: Speed Dial und Safe Areas

    Ein React Floating Action Button für den Daumen: Speed Dial mit 2 bis 6 Aktionen, einklappendes Label und Platz über Tab-Leiste und Home-Indikator.

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

    Native App Feeling für Web Apps: 12 React-Fixes

    Native App Feeling auf dem Handy: 12 Details für deine Web App, von sofortigen Taps über Action Sheets und wischbare Tabs bis zu Haptik und Large Titles.

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