React Swipeable Tabs: Seiten, die dem Daumen folgen

Du willst die Tabs aus nativen Apps im Web: Die Seiten liegen nebeneinander, ein Wischen zur Seite zieht die nächste unter deinem Daumen herein, und der Indikator in der Tab-Reihe gleitet mit, während sich die Seiten bewegen. React Swipeable Tabs sehen einfach aus, bis ein vertikales Scrollen den Tab wechselt oder der Indikator einen Frame hinter dem Finger herhinkt. Dieser Guide zeigt, was eine Web-Version richtig machen muss (das Wischen, den Indikator, vertikales Scrollen, Tastatur, reduzierte Bewegung), mit der Komponente Swipeable Tabs von Wingo UI. Wir bauen diese Bibliothek, wir sind also nicht neutral.
Was sind Swipeable Tabs, und wann passen sie?
Swipeable Tabs sind Tabs, deren Panels zugleich Seiten in einem horizontalen Pager sind. Ein Tipp auf einen Tab gleitet zu seiner Seite; ein Wischen über den Inhalt wechselt zur Nachbarseite und wählt ihren Tab. Sie passen zu Screens, auf denen die Tabs parallele Ansichten derselben Sache sind und man oft zur Nachbarseite wechselt: Ordner in einem Mail-Client, Aktiv / Überfällig / Interessenten in einer CRM-Liste, Tag / Woche / Monat in einem Kalender.
Weniger gut passen sie, wenn eine Seite eigene horizontale Gesten hat (eine Karte, ein Chart, über das du mit dem Finger fährst, ein Foto-Karussell) oder wenn die Tabs nichts miteinander zu tun haben. Für eine Einstellungsseite mit vier Formularen sind normale Tabs das bessere Bedienelement, und eine Auswahl, die nur die Daten auf dem Screen ändert, etwa ein Zeitraum, ist ein Segmented Control. Der Guide zum React Segmented Control erklärt diese Trennung im Detail.
Warum nicht einfach react-swipeable-views nutzen?
Weil das Paket immer noch React 17 als neueste unterstützte Version angibt und der Browser diese Arbeit heute selbst erledigt. Wer in React zwischen Tabs wischen wollte, griff jahrelang zu react-swipeable-views, dem Paket für Swipeable Views hinter dem Full-Width-Beispiel in der Tabs-Demo von Material UI v4 (opens in a new tab). Das neueste Release auf npm (opens in a new tab) ist 0.14.2, veröffentlicht am 22. Juni 2026, und als Peer Dependencies stehen dort weiter React 15, 16 und 17 (geprüft am 16. Oktober 2026). In einem React-19-Projekt bricht npm ab Version 7 die Installation mit einem Peer-Dependency-Konflikt ab, außer du übergibst --legacy-peer-deps. Die aktuelle Doku zu MUI Tabs (opens in a new tab) zeigt keine Swipe-Demo mehr (geprüft am 16. Oktober 2026).
Eine Karussell-Bibliothek, die an den Tab-Wert gekoppelt ist, geht auch. Dann kommen das Ziehen, die Entscheidung über die Achse und der Schwung aber aus JavaScript statt aus dem Scroller des Browsers. Die React-Aria-Doku von Adobe geht in ihrem Beispiel für Swipeable Tabs (opens in a new tab) den nativen Weg und baut auf CSS Scroll Snapping, Scroll-driven Animations und Anchor Positioning auf. Nutzt deine App schon React Aria Components, fang dort an. Die Komponente von Wingo UI nutzt denselben Scroll-Snap-Pager und steuert den Indikator stattdessen über einen Motion Value.
Wie baust du Swipeable Tabs in React ein?
Installier die Komponente, übergib pro Tab eine Option mit ihrer Seite und gib dem Elternelement eine Höhe. Die CLI kopiert auch alles, was die Datei importiert: Badge, die Helfer für Haptics, Motion, Tones und UI-Config sowie zwei kleine Hooks:
Jedes Objekt in options nimmt value, label, optional icon, badge oder disabled und seine Seite in content. Mit dem Standardwert layout="fill" füllen die Tabs die Höhe ihres Elternelements, deshalb braucht das Elternelement eine feste Höhe:
onValueChange wird aufgerufen, wenn ein Wischen auf einer neuen Seite zur Ruhe kommt, ein Tab gedrückt wird oder die Pfeiltasten einen auswählen. Eine Zahl in badge erscheint als Zähler, der bei 0 verschwindet.
$ npx wingo-ui@latest add swipeable-tabsWie folgt der Indikator dem Wischen?
Der Indikator liest die Position des Scrollers bei jedem Scroll-Event und wartet nie auf React. Die Seiten liegen in einem nativen horizontalen Scroller mit scroll-snap-type: x mandatory, und jede Seite hat scroll-snap-stop: always. Ein Wischen bewegt also höchstens eine Seite, und der Schwung kommt vom Browser selbst. Ein passiver Scroll-Listener macht aus der Position eine einzige Zahl, progress, einen Motion Value, bei dem 0 die erste Seite ist und 1,5 die Mitte zwischen der zweiten und dritten.
Alles, was sich bewegt, leitet sich aus dieser Zahl ab. Die Tab-Reihe misst die Offsets der Trigger nach jedem Render (und noch einmal, wenn Schriften laden oder ein Badge wächst), und Position und Breite des Indikators sind lineare Interpolationen zwischen den beiden Tabs links und rechts von progress. Die Unterstreichung ist ein fester Balken von 100px, der auf die Breite des Labels skaliert wird, so bleibt sie ein Transform. Pills und der Segmented Thumb bekommen eine Breite, weil das Skalieren einer Kapsel ihre runden Enden verzerren würde. Nichts davon löst pro Frame einen React-Render aus. Die Farbe des Labels wechselt bei der Hälfte des Wegs, und das kostet nur eine Handvoll Renders pro Wischen.
Dieselbe Zahl bekommen deine eigenen Teile über useSwipeableTabs(). Hier ist ein dünner Fortschrittsbalken unter der Tab-Reihe, gebaut mit der Parts-API:
Die Panels müssen in derselben Reihenfolge stehen wie die Trigger, denn Seite 2 im Scroller ist Tab 2.
Wann zählt ein Wischen als Tabwechsel?
Wenn der Scroller auf einer Seite stehen geblieben ist und kein Finger mehr aufliegt. Den Tab schon bei jedem Scroll-Event zu wechseln ist der Bug, der sich in selbst gebauten Scroll Snap Tabs am leichtesten einschleicht. Die Komponente schützt vor drei Varianten davon:
- Zur Ruhe kommen. Sie wartet auf
scrollend; in Browsern ohne dieses Event gilt das Scrollen nach 150ms ohne Scroll-Events als beendet. Solange noch ein Finger aufliegt, zählt das nicht. Ein Finger, der mitten im Wischen innehält, wählt also nie einen Tab aus. - Programmatisches Gleiten. Ein Tipp auf Tab 4, während Tab 1 offen ist, scrollt sanft über die Seiten 2 und 3 hinweg. Die Komponente merkt sich das Ziel und ignoriert das Anhalten auf den Seiten dazwischen.
onValueChangefeuert also einmal, für Tab 4, statt dreimal. Der haptische Tick spielt nur, wenn ein Finger, ein Trackpad oder ein Ziehen mit der Maus die Seite bewegt hat. - Krumme Breiten.
clientWidthist gerundet, die Snap Points sind es nicht. In einem 393,5px breiten Container landetscrollLeftdeshalb einen Bruchteil eines Pixels nebenpage * width, und die Abweichung wächst um bis zu einen halben Pixel pro Seite. Ein exakter Vergleich kann die hinteren Seiten verfehlen; die Komponente rechnet diese Abweichung ein.
Auch ein deaktivierter Tab wird hier übersprungen: Ein Wischen, das auf seiner Seite landet, läuft weiter bis zur nächsten aktivierten Seite.
Wie verhinderst du, dass vertikales Scrollen den Tab wechselt?
Das ist der klassische Bug bei selbst gebauten Tabs mit Wischgeste: Du scrollst eine lange Liste, dein Daumen driftet zur Seite, und der Screen springt zum nächsten Tab, weil ein Handler in JavaScript zu früh auf „horizontal“ entschieden hat.
Swipeable Tabs überlassen die Entscheidung dem Browser. Mit layout="fill" ist jede Seite ein eigener vertikaler Scroller (overflow-y: auto mit overscroll-behavior-y: contain) im horizontalen Pager, und der Browser gibt jede Geste an den Scroller, der zu ihrer Richtung passt. Eine überwiegend vertikale Bewegung scrollt die Liste, eine überwiegend horizontale blättert die Seite um. Jede Seite behält auch ihre eigene Scroll-Position: Wischst du weg und wieder zurück, landest du dort, wo du aufgehört hast.
Auf echten Screens kommen zwei weitere Fälle vor:
- Eine horizontale Leiste in einer Seite. Eine Reihe aus Chips oder Karten, die seitlich überläuft, scrollt zuerst. Hat die Leiste ihr Ende erreicht, blättert das nächste Wischen die Seite um.
- Eine Fläche, die horizontales Ziehen für sich braucht. Eine Karte, ein Range-Slider oder ein Unterschriftenfeld sollen nie die Seite umblättern. Markier sie mit
data-no-swipe, dann bleiben horizontale Wischbewegungen, die dort beginnen, bei ihr:
Auf dem Desktop blättert ein Wischen mit zwei Fingern auf dem Trackpad die Seiten um. Ein Ziehen mit der Maus tut das standardmäßig nicht, damit du Text weiter markieren kannst. Mit swipeWithMouse bewegt ein Ziehen über ein Viertel der Breite oder ein schnelles Schnippen eine Seite.
$ npx wingo-ui@latest add swipeable-tabsWie funktionieren Tastatur und Screenreader?
Die Tab-Reihe folgt dem WAI-ARIA-Pattern für Tabs (opens in a new tab): eine tablist aus tab-Buttons mit aria-selected und aria-controls und pro Seite ein tabpanel, das von seinem Tab beschriftet wird. Die Tabulatortaste landet auf dem ausgewählten Tab, Links und Rechts wechseln zwischen den Tabs (gespiegelt in Layouts von rechts nach links, deaktivierte Tabs werden übersprungen, an den Enden geht es auf der anderen Seite weiter), Pos1 und Ende springen, und ein weiterer Druck auf die Tabulatortaste führt in die Seite.
Die APG empfiehlt, schon beim Fokussieren auszuwählen (automatische Aktivierung), wenn die Panels ohne spürbare Verzögerung erscheinen. Das ist hier der Standard, weil die Nachbarseiten schon gemountet sind. Lädt eine Seite beim Öffnen Daten, setz activationMode="manual": Dann bewegen die Pfeile nur den Fokus, und Enter oder Leertaste wählen aus.
Das Wischen bringt zwei Details mit. Seiten außerhalb des Bildschirms sind inert, also landet die Tabulatortaste nie in einem Feld, das du nicht siehst. Trägt ein Wischen das fokussierte Feld davon, wandert der Fokus zum Tab, der jetzt zu sehen ist. Die Tab-Liste trägt außerdem eine versteckte Beschreibung, standardmäßig das englische „Swipe the content left or right to switch tabs.“, die du über die Prop labels übersetzen kannst.
Wie verhalten sich Swipeable Tabs bei reduzierter Bewegung?
Ein Wischen ist direkte Manipulation: Die Seite liegt unter deinem Finger, also bewegt sie sich mit ihm, egal was eingestellt ist. Was sich unter prefers-reduced-motion: reduce ändert, ist alles, was die Komponente selbst animiert. Ein Tipp auf einen Tab springt zur Seite, statt dorthin zu gleiten, der Indikator springt mit, die Tap-Skalierung der Trigger ist aus, und Seiten, die ohne Wischen wechseln, blenden nur über. Die Einstellung kommt aus usePrefersReducedMotion in lib/motion, das keine Hydration-Fehler auslöst; der Guide zu prefers-reduced-motion erklärt, warum das wichtig ist.
Welche Props von Swipeable Tabs sind am wichtigsten?
Die meisten Screens brauchen nur options, value und onValueChange; der Rest sind Standardwerte, die du für einen bestimmten Screen änderst.
size, tone, color, radius und classNames folgen derselben Skala wie der Rest der Bibliothek. Touchscreens bekommen in der Standardgröße Trigger mit 44px.
Auf Android nutzt der Tick navigator.vibrate, und zwar erst, nachdem die Seite einen Tipp gesehen hat. Auf dem iPhone weicht der Helfer auf den System-Tick eines versteckten nativen Schalters aus, den iOS ab Version 26.5 nur abspielt, wenn die Person selbst darauf tippt. Ein Wischen, das zur Ruhe kommt, bleibt dort also still (Stand Oktober 2026; der Guide zur Web Vibration API hat die Quellen). Sieh den Tick als Bonus, nie als einziges Feedback.
Was geht bei Swipeable Tabs auf echten Screens schief?
Vier Fehler passieren leicht, wenn du das in eine App einbaust, und jeder lässt sich mit einer Zeile beheben.
- Keine Höhe am Elternelement. Mit
layout="fill"nehmen die Tabsh-full. In einem Elternelement ohne Höhe fallen die Seiten in sich zusammen. Nimm einen Screen mitflex h-dvh flex-coloder wechsle für Tabs in einer Karte zulayout="auto". - Schwere Seiten, die alle auf einmal mounten. Lass
lazyan: Der erste Render mountet nur die aktive Seite und ihre Nachbarn. Stapeln sich besuchte Seiten, setzkeepMounted={false}. Dann werden Seiten, die mehr als eine Position entfernt liegen, nach einem Wischen wieder ausgehängt und verlieren ihre Scroll-Position. - Ein kontrollierter Wert, der sich mitten im Wischen ändert. Die Komponente übernimmt ihn, wenn das Wischen zur Ruhe gekommen ist, statt gegen den Finger zu kämpfen. Versuch also nicht, das mit einem Timeout zu „reparieren“.
- Zu viele Tabs. Laufen die Labels über, scrollt die Reihe seitlich mit ausgeblendeten Rändern und hält den aktiven Tab beim Wischen sichtbar. Mit dem Standardwert
fullWidthrücken überlaufende Labels der Variante underline zuerst bis auf einen knappen Mindestabstand zusammen. Ab vier oder fünf Labels auf einem 360px breiten Smartphone setzt du deshalbfullWidth={false}, damit jedes Label sein normales Padding bekommt.
Womit solltest du anfangen?
Braucht dein Screen nur Tabs, die auf Tipps reagieren, fang mit den kostenlosen Tabs an (npx wingo-ui@latest add tabs). Für Seiten, die dem Daumen folgen, gehören Swipeable Tabs zu Wingo UI Pro, und die Live-Demo auf ihrer Doku-Seite funktioniert auf deinem Smartphone, bevor du irgendetwas installierst. Weitere Patterns wie dieses findest du in der Kategorie Mobile UI in React und im Guide zu Mobile-first React Components.
Häufige Fragen
Wie wische ich in React ohne Bibliothek zwischen Tabs?
Leg pro Tab ein Panel in voller Breite in einen Container mit scroll-snap-type: x mandatory, teil beim Scrollen scrollLeft durch clientWidth, um den Indikator zu bewegen, und wähl den neuen Tab aus, sobald das Scrollen zur Ruhe kommt. Die Tab-Semantik, die Tastatur, den Fokus und den kontrollierten Wert musst du trotzdem selbst schreiben.
Funktioniert react-swipeable-views mit React 19?
Das neueste Release, 0.14.2 vom Juni 2026, nennt React 15, 16 und 17 als Peer Dependencies. In einem React-19-Projekt meldet npm deshalb einen Peer-Dependency-Konflikt (npm-Registry, geprüft am 16. Oktober 2026). Ein nativer Scroll-Snap-Container braucht überhaupt kein zusätzliches Paket.
Warum wechselt ein vertikales Scrollen meinen Tab?
Meist schnappt sich ein Gesten-Handler in JavaScript die Ziehbewegung, bevor der Browser ihre Richtung entschieden hat. Ist der Pager ein nativer horizontaler Scroller und scrollt jede Seite vertikal für sich, gibt der Browser jede Geste an den Scroller, der zu ihrer Richtung passt. Eine überwiegend vertikale Bewegung scrollt also die Seite.
Sind Swipeable Tabs barrierefrei?
Ja, wenn die Tab-Reihe eine Tablist aus echten Buttons mit aria-selected und aria-controls ist, die Pfeiltasten zwischen den Tabs wechseln und Seiten außerhalb des Bildschirms inert sind. Das Wischen ist ein Extra: Jeder Tab muss sich auch per Tipp und per Tastatur öffnen lassen.
Sind die Swipeable Tabs von Wingo UI kostenlos?
Nein, Swipeable Tabs gehören zu Wingo UI Pro. Tabs und das Segmented Control sind kostenlos und lassen sich mit npx wingo-ui@latest add tabs oder add segmented-control installieren.
- Mobile UI
- React
- Gesten
- Barrierefreiheit
- Tailwind CSS


