Tailwind Touch Manipulation: touch-action für jedes Control

Tipp auf dem iPhone dreimal schnell auf einen Plus-Button, und die Seite zoomt hinein, statt bis drei zu zählen. Zieh den Griff eines Sliders, und die Seite scrollt mit. Wisch ein Karussell zur Seite, und der ganze Screen rutscht nach oben. Hinter jedem dieser Bugs steckt eine fehlende CSS-Eigenschaft, touch-action, und die Utilities für Tailwind Touch Manipulation setzen sie pro Element: touch-manipulation, touch-none, touch-pan-y und sieben weitere. Dieses Tutorial zeigt, was jeder Wert macht, welches Control welchen braucht und welche Tap-Highlight- und Zoom-Bugs sie beheben. Dazu kommen die Klassen, die wir in Button, Slider und Drawer von Wingo UI ausliefern.
Der Artikel gehört zu unserer Reihe Mobile UI in React. Der Leitfaden Mobile-first React-Komponenten stellt diese Regeln neben Touch Targets und Safe Areas.
Was steuert touch-action eigentlich?
Welche Gesten der Browser selbst übernimmt, wenn ein Finger auf einem Element landet. Standardmäßig gehören Panning (Scrollen) und Pinch-Zoom dem Browser. Sobald er ein Panning startet, hören deine Pointer Events auf: Du bekommst ein pointercancel und kein weiteres pointermove (MDN (opens in a new tab)). Deshalb funktioniert ein Drag auf Basis von Pointer Events mit der Maus und bricht auf dem Smartphone ab, sobald sich der Finger bewegt.
Drei Regeln aus der Spezifikation erklären die meisten Überraschungen:
- Der Wert wird nicht vererbt, erreicht die Kinder aber trotzdem. Beginnt eine Geste, bildet der Browser die Schnittmenge aus den
touch-action-Werten des berührten Elements und seiner Vorfahren, bis zum nächsten Scroll-Container. Eintouch-manipulationauf einem Button gilt also auch für das Icon darin. - Er endet am Scroll-Container. Werte oberhalb des scrollenden Elements gelten nicht für eine Berührung darin. Ein Sheet mit
touch-action: nonekann trotzdem eine Liste enthalten, die nativ scrollt. Das nutzen wir weiter unten beim Drawer. - Er wird beim Start der Geste gelesen. Änderst du ihn mitten im Ziehen, wirkt sich das auf diese Geste nicht mehr aus.
Welche Utilities für Tailwind touch-action gibt es?
Zehn, jede für einen Wert (Tailwind-Doku (opens in a new tab)):
Die Pan- und Zoom-Klassen lassen sich kombinieren. In Tailwind CSS 4.3.3 (der Version, die wir im Oktober 2026 geprüft haben) setzt jede eine Variable wie --tw-pan-y, und eine einzige Deklaration liest sie alle: touch-action: var(--tw-pan-x,) var(--tw-pan-y,) var(--tw-pinch-zoom,). Aus touch-pan-y touch-pinch-zoom wird also pan-y pinch-zoom, eine Fläche zum seitlichen Wischen, auf der man trotzdem zoomen kann. Varianten funktionieren wie gewohnt, zum Beispiel touch-pan-y md:touch-auto.
Was behebt touch-manipulation auf Buttons?
Den Doppeltipp-Zoom. manipulation erlaubt Panning und Pinch-Zoom und schaltet den Rest ab, vor allem den Zoom per Doppeltipp. MDN beschreibt den Wert als Alias für pan-x pan-y pinch-zoom und merkt an, dass der Browser Klicks ohne Doppeltipp-Zoom nicht mehr verzögern muss.
Die 300ms-Verzögerung durch den Doppeltipp-Zoom ist größtenteils Geschichte. Chrome hat sie in Version 32 für Seiten mit width=device-width abgeschafft, Safari folgte mit iOS 9.3 (Beitrag von Chrome (opens in a new tab)). Der Beitrag von WebKit zu schnellem Tippen (opens in a new tab) (Dezember 2015) ergänzt das Detail, das bis heute zählt: Der schnelle Weg gilt nur, solange die Seite auf ihrer anfänglichen Zoomstufe steht. Hat jemand hineingezoomt, wartet Safari wieder auf einen möglichen zweiten Tap. Mit touch-action: manipulation reagieren Taps auf jeder Zoomstufe sofort.
Der Bug, den du heute noch siehst, ist der Zoom selbst. Der Plus-Button eines Steppers, eine Taste im Taschenrechner oder ein zweimal hintereinander angetipptes „Mehr laden“ kann als Doppeltipp zählen und die Seite zoomen. Willst du den Zoom beim Doppeltippen verhindern, setz die Klasse auf das Control:
Setz sie auf Controls, nicht auf html oder body. Mit dem Doppeltipp-Zoom auf Text lesen manche Menschen Kleingedrucktes. Schaltest du ihn für die ganze Seite ab, nimmst du ihnen das, nur um ein Problem zu lösen, das allein deine Controls haben.
Der kostenlose Button von Wingo UI trägt touch-manipulation, select-none und das Zurücksetzen des Tap-Highlights in seinen eigenen Basisklassen. Ein kopierter Button behält sie also in einem Projekt mit beliebigem globalem Stylesheet:
$ npx wingo-ui@latest add buttonWie gehst du mit der Tap Highlight Color in Tailwind um?
Meist, indem du einen Pressed-State ergänzt, denn Tailwind hat das Highlight schon entfernt. -webkit-tap-highlight-color legt das halbtransparente Rechteck fest, das Safari und Chromium über einen angetippten Link oder Button zeichnen. Die Eigenschaft ist nicht standardisiert, wird vererbt und von WebKit und Blink unterstützt, nicht von Firefox (MDN (opens in a new tab)).
Der Preflight von Tailwind CSS v4 setzt -webkit-tap-highlight-color: transparent auf html (in 4.3.3 erklärt der Kommentar daneben, dass das die Tap-Highlights auf iOS abschaltet). Weil die Eigenschaft vererbt wird, startet jedes Element in einer Tailwind-v4-App ohne Highlight. Das hat einen Preis, den viele übersehen: Ein einfaches <a> oder <button> ohne active:-Style zeigt bei einem Tap überhaupt nichts an.
In einer Tailwind-v4-App laufen die Regeln also andersherum:
- Jedes drückbare Element bekommt einen Pressed-State.
active:bg-foreground/10,active:scale-[0.97]oder ein Spring-Press wie beim Button. - Komponenten, die du teilst, setzen den Wert selbst. Code, der in ein Projekt ohne Preflight kopiert wird oder in eines, das die Eigenschaft zurücksetzt, blitzt wieder grau auf. Nutze in der Komponente die Arbitrary Property
[-webkit-tap-highlight-color:transparent]oder definiere einmal eine Utility:
- Entferne das Highlight nie ohne Ersatz. Hat ein Control keinen Pressed-Style, ist ein sichtbares Tap-Highlight besser als gar nichts.
Wann nutzt du touch-none in Tailwind?
Wenn dein Code auf dem Element jede Bewegungsrichtung behandelt und es dich nicht stört, dass ein Swipe, der dort beginnt, nie die Seite scrollt. Das passt zu einem Slider, einem Signature Pad, einem Zeichen-Canvas, einer Karte oder einem Crop-Handle. Für alles Große ist es die falsche Wahl: Ein bildschirmfüllendes Element mit touch-none hält Leute fest, die daran vorbeiscrollen wollen. MDN warnt außerdem, dass none auch den Pinch-Zoom blockiert, auf den Menschen mit Sehschwäche angewiesen sind.
Der Slider von Wingo UI zeigt, wie du das Element klein hältst. Nur das Root-Element hat touch-none, eine schmale Zeile, die bei Coarse Pointern (Touch) per Pseudo-Element eine Trefferfläche von 44px bekommt. Ein Ziehen darauf scrollt nie die Seite, während der Rest des Formulars normal scrollt:
$ npx wingo-ui@latest add sliderDie Griffe (Thumbs) setzen zusätzlich touch-manipulation, damit auch ein schneller Doppeltipp auf einen Griff nie zoomt.
Wann nutzt du touch-pan-y oder touch-pan-x?
Wenn sich dein Element auf einer Achse bewegt und die Seite auf der anderen scrollt. Eine Zeile, die du zum Löschen nach links wischst, eine Karte, die du wegwischst, oder ein Slide im Karussell bekommen alle touch-pan-y: Der Browser behält das vertikale Scrollen, und eine horizontale Bewegung geht an deine Pointer-Handler.
Mit eigenen Pointer-Handlern wie diesen ist die Klasse Pflicht. Ohne sie behandelt der Browser die Berührung als mögliches Scrollen, und eine Bewegung mit auch nur leichter vertikaler Abweichung endet in pointercancel. setPointerCapture ist die andere Hälfte: Die Zeile bekommt weiter Bewegungen, auch wenn der Finger von ihr abrutscht. Die Handler schreiben das transform direkt ins Element, statt bei jeder Bewegung setState aufzurufen. Motion erledigt das für dich. Sein drag="x" schreibt touch-action: pan-y als Inline-Style (pan-x bei drag="y", none bei drag), und ein Inline-Style gewinnt gegen jede Klasse, die du ergänzt. Unsere Tutorials React Swipe to Delete und React Swipe Cards bauen beide Muster.
touch-pan-x ist der gespiegelte und seltenere Fall: ein vertikales Ziehen, etwa an einem Handle zum Umsortieren, in einer Zeile, die seitlich scrollt. Für sortierbare Listen vergleicht das Tutorial zu dnd kit auf dem Smartphone die Kombinationen aus touch-action und Sensoren, mit denen ein Long Press zieht und ein Swipe trotzdem scrollt.
Wie lassen sich Sheets gleichzeitig ziehen und scrollen?
Indem du dich auf die Regel verlässt, dass touch-action am Scroll-Container endet. vaul, die Bibliothek hinter dem kostenlosen Drawer, setzt touch-action: none auf das ganze Sheet. Ein Ziehen irgendwo auf Header oder Footer bewegt also das Sheet. Der Body des Drawers ist ein eigener Scroll-Container (overflow-y-auto overscroll-contain). Für eine Berührung im Body zählen deshalb nur die Werte bis zum Body selbst, und die Liste darin scrollt nativ. vaul bewegt das Sheet erst, wenn die Liste wieder ganz oben ist.
$ npx wingo-ui@latest add draweroverscroll-contain übernimmt die andere Hälfte: Erreicht die Liste ihr Ende, setzt sich das Scrollen nicht auf die Seite hinter dem Sheet fort. touch-action steuert dieses Scroll Chaining gar nicht, das macht overscroll-behavior. Für ein Sheet, dessen Inhalt eigene horizontale Gesten hat, etwa ein Karussell oder eine Karte, gibst du handleOnly mit, damit es sich nur am Handle ziehen lässt. Der Guide zum React Bottom Sheet (auf Englisch) behandelt Snap Points und die Tastatur.
Welche Klasse braucht welches Control?
Wie testest du touch-action?
Lies zuerst den berechneten Wert: Im Web Inspector von Safari oder in den Chrome DevTools zeigt der Tab Computed den touch-action-Wert des Elements. Der Wert der Eltern zählt ebenfalls, bis zum nächsten Scroller. Der Device Mode von Chrome emuliert Touch. So prüfst du, dass ein Ziehen auf einer Fläche mit touch-none nicht scrollt. Doppeltipp-Zoom und Tap-Highlights brauchen ein echtes Smartphone: Tipp deinen Stepper auf einem iPhone und einem Android-Smartphone schnell hintereinander an, zoom in die Seite hinein und tipp noch einmal.
In Playwright kannst du in einem Context mit hasTouch: true page.touchscreen.tap() aufrufen. Das reicht, um zu prüfen, dass Klicks feuern. Die Gestenerkennung des Browsers bildet es aber nicht nach, also teste das Zoomverhalten weiter auf einem echten Gerät.
Wo fängst du am besten an?
Durchsuch deine Codebase nach onClick auf Elementen ohne touch-manipulation und nach Drag-Handlern ohne touch-*-Klasse. Diese beiden Listen sind meist schon der ganze Fix. Prüf dann, ob jedes drückbare Element einen Pressed-State hat, denn Tailwind v4 hat das Tap-Highlight bereits ausgeblendet. Wenn du lieber mit Komponenten startest, die diese Klassen schon tragen: Button und Drawer sind kostenlos, npx wingo-ui@latest add button drawer. Der Slider gehört zu Wingo UI Pro.
Häufige Fragen
Was macht touch-manipulation in Tailwind?
Die Klasse setzt touch-action: manipulation. Der Browser darf scrollen und per Pinch zoomen, andere Gesten wie der Doppeltipp-Zoom sind aus. Ohne Doppeltipp-Zoom muss der Browser nicht auf einen zweiten Tap warten, Taps auf dem Element feuern also sofort.
Gibt es auf dem Smartphone noch die 300ms-Verzögerung beim Tippen?
Nicht auf Seiten mit width=device-width auf der anfänglichen Zoomstufe: Chrome hat sie in Version 32 entfernt, Safari mit iOS 9.3. WebKit hat dokumentiert, dass der schnelle Tap nur auf der anfänglichen Zoomstufe greift, und schnelle Taps können weiterhin den Doppeltipp-Zoom auslösen. Genau den schaltet touch-manipulation pro Element ab.
Wann nutze ich touch-none in Tailwind?
Nur auf Elementen, auf denen dein Code jede Bewegungsrichtung behandelt, etwa ein Slider, ein Signature Pad, ein Canvas oder eine Karte. Ein Swipe, der dort beginnt, scrollt oder zoomt die Seite nie. Halte solche Elemente also klein oder lass der Seite einen anderen Bereich zum Scrollen.
Wie entferne ich die Tap Highlight Color in Tailwind?
In Tailwind CSS v4 musst du das meist nicht: Der Preflight setzt -webkit-tap-highlight-color: transparent auf html, und die Eigenschaft wird vererbt. Ohne Preflight nimmst du die Arbitrary Property [-webkit-tap-highlight-color:transparent] und ergänzt immer einen active- oder Pressed-Style, damit ein Tap sichtbar bleibt.
Kann ich touch-pan-y und touch-pinch-zoom in Tailwind kombinieren?
Ja. In Tailwind CSS v4 schreiben die Pan- und Pinch-Utilities eigene CSS-Variablen, die eine einzige touch-action-Deklaration liest. touch-pan-y touch-pinch-zoom wird also zu touch-action: pan-y pinch-zoom.
- Tailwind CSS
- Touch
- Mobile
- CSS
- Barrierefreiheit


