Shadcn Sidebar Mobile: Sheet reparieren, Tab Bar ergänzen
Beim Thema shadcn sidebar mobile fühlen sich die meisten Admin-Layouts zum ersten Mal kaputt an. Am Laptop klappt die Sidebar zu Icons zusammen, merkt sich ihren Zustand und reagiert auf Cmd+B. Am Handy gleitet sie als Sheet herein, du tippst auf einen Link, dahinter wechselt die Seite, und das Sheet bleibt einfach offen. Ältere Kopien loggen zusätzlich eine Warnung wegen eines fehlenden DialogTitle, und die letzte Zeile der Seite verschwindet hinter der Toolbar von Safari. Dieses Tutorial erklärt, woher jedes dieser Probleme kommt, behebt sie im Standard-shadcn/ui und baut dann eine App Shell, die aus der Sidebar unter 768px einen Drawer plus eine Bottom Tab Bar macht.
Wie funktioniert die shadcn Sidebar auf dem Handy?
Sie rendert einen anderen Baum. Stand November 2026 prüft der Quellcode der shadcn/ui Sidebar (opens in a new tab) isMobile aus seinem Hook useIsMobile. Ist der Wert true, gibt er statt der festen Spalte ein Sheet auf Basis von Radix zurück:
- Der Breakpoint liegt bei 768px (
MOBILE_BREAKPOINT = 768inhooks/use-mobile.ts). - Das Sheet ist
18rembreit (SIDEBAR_WIDTH_MOBILE), die Desktop-Spalte16rem, die Icon-Leiste3rem. - Das shadcn Sidebar Sheet hat seinen eigenen State.
SidebarProviderhältopenfür den Desktop und ein separatesopenMobilefür das Sheet. Deine PropsopenundonOpenChangeam Provider steuern nur den Desktop-Zustand. toggleSidebar, dasSidebarTriggerund Cmd/Ctrl+B aufrufen, schaltet am HandyopenMobileum und am Desktopopen.collapsible="none"überspringt den mobilen Zweig komplett: Diese Sidebar rendert bei jeder Breite als normale Spalte, ohne Sheet.
Diese Trennung erklärt die meisten Bugs, die als „shadcn sidebar not working on mobile“ gemeldet werden. Code, der die Sidebar mit setOpen(false) schließt, bewirkt am Handy nichts, und ein kontrolliertes open-Prop erreicht das Sheet nie.
Warum schließt die shadcn Sidebar nicht, wenn du auf einen Link tippst?
Nichts in der Komponente schließt sie. Das ist der Bug hinter Suchen wie „shadcn sidebar close on click mobile“ oder „shadcn sidebar mobile schließen“. In derselben Quelldatei ändert sich openMobile nur über setOpenMobile, und das läuft im Trigger, im Handler für Cmd+B und im onOpenChange des Sheets (Tipp auf das Overlay und Escape). SidebarMenuButton ist ein gestylter Button ohne Logik zum Schließen, und es gibt keinen Effect, der die Route beobachtet.
Der verlässliche Fix: Schließ das Sheet im Click-Handler des Links.
Eine verbreitete Alternative ist ein Effect auf usePathname(), der setOpenMobile(false) aufruft. Wir nehmen lieber den Click-Handler, aus zwei Gründen. Ein Tipp auf die Seite, auf der du schon bist, ändert den Pathname nicht, also feuert der Effect nie, und das Sheet bleibt offen. Und bei einer langsamen Route ohne loading.tsx ändert sich der Pathname erst, wenn der Server antwortet. Bis dahin liegt das Sheet offen über der alten Seite. Schließt du schon beim Tippen, gibt es sofort Feedback, egal was die Route macht.
Wie behebst du die Warnung wegen des fehlenden DialogTitle?
Ergänz einen visuell versteckten SheetHeader mit Titel. Das Sheet der shadcn Sidebar ist ein Radix-Dialog, und Radix loggt „DialogContent requires a DialogTitle“, wenn ein Dialog keinen Titel hat, weil ein Screenreader sonst nichts ansagen kann. Das shadcn-ui Issue #5746 (opens in a new tab), eröffnet am 6. November 2024, meldet genau das für die Sidebar.
Der aktuelle Quellcode enthält den versteckten Header schon. Wurde deine components/ui/sidebar.tsx früher erzeugt, fehlt er im mobilen Zweig wahrscheinlich. Setz ihn in SheetContent:
Importier SheetHeader, SheetTitle und SheetDescription oben in der Datei aus @/components/ui/sheet. Wähl einen Titel, den jemand mit Screenreader versteht: „Navigation“ oder der Name deiner App sagt mehr als das Standard-„Sidebar“.
Was geht an der shadcn Sidebar auf Handys sonst noch kaputt?
Das Sheet öffnet sich nach dem Drehen wieder
Nichts setzt openMobile zurück, wenn isMobile wechselt. Öffne das Sheet am Handy, dreh ins Querformat (die meisten aktuellen Handys sind dort breiter als 768px), dreh zurück, und das Sheet ist wieder offen. Ein einziger Effect im Provider behebt das:
Render <ResetMobileSidebar /> irgendwo innerhalb von SidebarProvider.
Der erste Render hält sich für den Desktop
useIsMobile startet mit undefined, gibt false zurück und misst das Fenster in einem Effect. Die Desktop-Spalte ist unter md per CSS ausgeblendet, die Sidebar selbst flackert also nicht. Alles, was du für sichtbares Markup an isMobile verzweigst, flackert dagegen schon: Ein Header, der am Handy einen anderen Button rendert, tauscht ihn nach der Hydration aus. Schalte sichtbares Layout mit Tailwind-Breakpoints um und nutz den Hook nur für Verhalten. Unser Tutorial zum SSR-sicheren use mobile Hook vergleicht die Varianten.
Die Seite wird abgeschnitten oder die Toolbar verschwindet nie
Lass das Dokument scrollen. Damit die Sidebar stehen bleibt, setzt ein verbreitetes Muster h-screen overflow-y-auto auf SidebarInset. Dadurch wird das Inset zu einem eigenen Scroll-Container. In Safari auf iOS ist 100vh die Höhe bei ausgeblendeter Toolbar, also liegen die letzten Zeilen unter der Toolbar. Und weil das Fenster nie scrollt, wird die Toolbar nie kleiner. Der shadcn-Wrapper nutzt bereits min-h-svh, und die Desktop-Spalte ist h-svh und fixiert. Du kannst also das Dokument scrollen lassen und deinen Header sticky top-0 machen. Der Guide zu 100vh auf Mobilgeräten erklärt svh, dvh und lvh im Detail.
Der Modus shadcn sidebar collapsible icon tut am Handy nichts
collapsible="icon" formt nur die Desktop-Spalte: eine 3rem breite Leiste mit Tooltips. Unter 768px öffnet sich die Sidebar immer als 18rem breites Sheet mit vollen Labels. Für Touch ist das richtig, denn Tooltips brauchen Hover. Es heißt aber auch, dass die Icon-Leiste kein Navigationsmuster für Handys ist.
Warum kombinierst du die Sidebar am Handy mit einer Bottom Tab Bar?
Weil die Seiten, die Leute jeden Tag öffnen, dort liegen sollten, wo der Daumen schon ist. Ein Drawer versteckt die Navigation hinter einem Tipp auf einen 44px-Button in der oberen Ecke, also an der Stelle, die mit einer Hand am schwersten zu erreichen ist. Apps, die Leute täglich öffnen, legen ihre drei bis fünf wichtigsten Ziele an den unteren Rand und lassen die lange Liste in einem Drawer oder einem Mehr-Tab. shadcn/ui hat keine Tab-Bar-Komponente, du müsstest sie also selbst bauen: ein fixiertes nav mit md:hidden, pb-[env(safe-area-inset-bottom)] für den Home-Indikator, einen aktiven Zustand aus dem Pathname und Padding unten auf der Seite, damit die letzte Zeile frei bleibt. Unser Tutorial zur React Bottom Navigation Bar zeigt diesen Aufbau samt Sonderfällen.
Wie baust du eine Sidebar, die zu Drawer plus Tab Bar wird?
Die App Shell von Wingo UI übernimmt die Umschaltung des Layouts für dich. Ab 768px rendert sie die Sidebar als Spalte, die sich zu einer 64px breiten Icon-Leiste zusammenklappen lässt (Mod+B, in einem Cookie gespeichert). Unter 768px wandert dieselbe Sidebar in einen Drawer, und die Bottom Tab Bar erscheint am unteren Rand. Der Drawer hat einen visuell versteckten Titel, schließt beim Navigieren, mit Escape, bei einem Tipp auf die abgedunkelte Seite oder wenn du ihn wegwischst, und gibt den Fokus an den Menü-Button zurück. Die Umschaltung läuft über CSS, das Server-HTML passt also zum Handy. Die Dateien landen als Quellcode in deinem Repo, gestylt mit Tailwind v4 und deinen eigenen Tokens.
$ npx wingo-ui@latest add app-shell1. Installier sie
Der Befehl installiert auch die Sidebar, die Top Bar, die Bottom Tab Bar und das Sheet, die sie nutzt.
2. Nutz eine gemeinsame Link-Komponente
Jedes Navigationsteil liest den Router aus einem LinkProvider. So weiß auch der Drawer, dass eine Navigation begonnen hat, und schließt sich beim Tippen, ganz ohne Effect auf den Pathname:
3. Definier die Navigation einmal und ergänz einen Mehr-Tab
Der Mehr-Tab hat kein href. Er verhält sich also wie ein Button und wird nie zum aktiven Tab. Weil du mobileOpen kontrollierst, öffnet er denselben Drawer wie der Menü-Button in der Top Bar.
4. Lies das Collapsed-Cookie auf dem Server
Schreib den Cookie-Namen hier als String. persistedCookieName liefert denselben Wert, aber sein Modul importiert React-Hooks, und der Import in eine Server Component scheitert mit „You're importing a module that depends on useInsertionEffect“. Liest das Layout das Cookie, ist eine eingeklappte Leiste schon beim ersten Paint eingeklappt. Das Standard-shadcn braucht denselben Schritt: Sein Provider schreibt ein Cookie sidebar_state, liest es aber nie. Solange dein Layout dieses Cookie nicht an defaultOpen übergibt, ist eine eingeklappte Sidebar nach jedem vollständigen Neuladen der Seite wieder ausgeklappt.
Wie sieht die Sidebar im Drawer aus?
Die Shell rendert eine zweite Kopie deiner Sidebar mit mode="sheet", die nur gemountet ist, solange der Drawer offen ist. In diesem Modus wachsen die Zeilen auf 44px mit 15px Schrift, der Button zum Einklappen verschwindet, und ein Tipp auf einen Link innerhalb der App schließt den Drawer. Am Desktop sind die Zeilen 32 oder 36px hoch und wachsen auf jedem Touchscreen trotzdem auf 44px. So bekommt auch ein Tablet im Querformat Flächen, die sich gut treffen lassen.
$ npx wingo-ui@latest add sidebarÜbergibst du eine eigene Wrapper-Komponente als sidebar, reich ihre Props (mode, variant, collapsible, width, className, style) an die Sidebar weiter. Sonst rendert die Kopie im Drawer als Desktop-Spalte.
Welche Props der App Shell zählen am Handy?
Vier Props bestimmen, wie sich das Layout am Handy verhält; die Standardwerte passen für die meisten Apps.
- Breakpoint.
mobileBreakpoint="lg"verschiebt die Umschaltung auf 1024px, dann bekommen auch Tablets Drawer und Tab Bar. - Scrollen. Behalte für Apps
scroll="window", den Standard: Das Dokument scrollt, die iOS-Toolbar wird kleiner, und die Scroll-Wiederherstellung des Routers funktioniert. Nimmscroll="content"nur für eingebettete Apps. - Wischen vom Rand.
edgeSwipeöffnet den Drawer mit einer Wischgeste von der Startkante. Schalte es nur in installierten PWAs ein, denn in Safari ist diese Kante die Zurück-Geste des Browsers. - Beim Scrollen ausblenden. Übergib statt des Item-Arrays ein
BottomTabBar-Element mithideOnScroll, wenn die Leiste beim Lesen wegrutschen soll.
Für ein komplettes Backoffice in einem Block verdrahtet die Admin Shell all das mit Workspace-Switcher, Suche, Benachrichtigungen, einem Befehlsmenü und einer Tab Bar mit fünf Tabs, deren Mehr-Tab den Drawer öffnet. Das Tutorial zum Next.js Admin-Dashboard baut ein Dashboard darauf auf, und weitere Muster fürs Handy findest du in der Kategorie Mobile UI in React und in unserem Guide zu Mobile-first React-Komponenten.
Was solltest du als Nächstes tun?
Bleibst du beim Standard-shadcn/ui, ergänz das Schließen per onClick, den versteckten Titel und den Reset-Effect von oben, und entferne jedes h-screen vom Inset. Öffne dann deine App bei 390px und tipp dich durch jeden Link. Willst du Drawer, Icon-Leiste und Tab Bar fertig haben, dann gehören App Shell, Sidebar und Bottom Tab Bar zu Wingo UI Pro; mit einem kostenlosen Konto testest du zuerst die kostenlosen Komponenten.
Häufige Fragen
Warum schließt die shadcn Sidebar auf dem Handy nicht, wenn ich auf einen Link klicke?
Die mobile Sidebar ist ein Sheet, dessen offener Zustand openMobile sich nur über den Trigger, das Overlay, Escape oder setOpenMobile ändert. SidebarMenuButton hat keine Logik zum Schließen, also ruf setOpenMobile(false) im onClick des Links auf, wenn isMobile true ist.
Wie behebe ich die Warnung „DialogContent requires a DialogTitle“ in der shadcn Sidebar?
Setz in das mobile SheetContent einen SheetHeader mit der Klasse sr-only, der einen SheetTitle und eine SheetDescription enthält. Der aktuelle shadcn-Quellcode macht das bereits; Kopien, die vor dieser Änderung erzeugt wurden, loggen die Warnung, bis du ihn ergänzt.
Funktioniert collapsible icon auf dem Handy in der shadcn Sidebar?
Nein. collapsible="icon" formt nur die Desktop-Spalte. Unter 768px öffnet sich die Sidebar als 18rem breites Sheet mit vollen Labels, und genau das willst du auf einem Touchscreen.
Ab welcher Breite wechselt die shadcn Sidebar in den mobilen Modus?
Ab 768px. Der Hook useIsMobile in shadcn/ui nutzt einen MOBILE_BREAKPOINT von 768 und gibt false zurück, bis er das Fenster nach dem Mount gemessen hat.
Sollte eine mobile App einen Sidebar-Drawer oder eine Bottom Tab Bar nutzen?
Beides. Leg die drei bis fünf Seiten, die Leute jeden Tag öffnen, in eine Bottom Tab Bar, wo der Daumen ohnehin liegt, und lass die komplette Navigation im Drawer hinter einem Menü-Button oder einem Mehr-Tab.
- Mobile UI
- Sidebar
- shadcn/ui
- Navigation
- React
- Next.js

