React Swipe to Delete: iOS-Swipe-Actions mit Undo

React Swipe to Delete ist das Muster, zu dem iPhone-Nutzer ganz ohne Nachdenken greifen: Zeile nach links ziehen, ein rotes „Löschen“ erscheint, weiterziehen, und die Zeile ist weg. Im Web kommt es meist als Drag-Handler daher, der mit dem vertikalen Scrollen kämpft, drei Zeilen gleichzeitig offen lässt, ohne Weg zurück löscht und auf dem Laptop gar nichts tut. Dieses Tutorial baut das iOS-Verhalten Schritt für Schritt mit motion nach und zeigt dann die List von Wingo UI. Sie bündelt Swipe Actions, Full Swipe, ein Long-Press-Sheet und ein Tastaturmenü, mit Undo über den kostenlosen Toast.
Wann fühlt sich Swipe to Delete wie unter iOS an?
Wenn fünf Verhaltensweisen stimmen. Web-Umsetzungen lassen oft einige davon weg. Ein Beispiel ist react-swipeable-list (opens in a new tab), ein Web-Paket für React Swipe Actions (1.10.0, veröffentlicht am 11. Oktober 2024, MIT, Stand Oktober 2026). Es hat einen iOS-Modus, eine Option fullSwipe, die standardmäßig aus ist, einen threshold von 0.5 und ein Flag destructive, das 1000ms wartet, bevor es deinen Callback aufruft; das README erwähnt keine Tastaturunterstützung. An dieser Liste messen wir unsere eigene Komponente:
- Die Zeile folgt dem Finger, und zwar nur seitwärts. Eine vertikale Bewegung scrollt die Seite. Eine horizontale schiebt die Zeile, und der Drag wird auf diese Achse festgelegt, sobald er beginnt.
- Ein kurzer Swipe rastet offen ein. Lässt du nach mehr als einem Viertel der Action-Breite los oder machst einen Flick, bleibt die Zeile mit freigelegten Buttons stehen. Alles darunter federt zurück.
- Ein Full Swipe führt die Aktion aus. Laut Apples SwiftUI-Doku zu swipeActions (opens in a new tab) kann man standardmäßig die erste Aktion einer Swipe-Richtung per Full Swipe auslösen. Diese erste Aktion ist der äußerste Button, meist Löschen.
- Immer nur eine offene Zeile. Öffnest du eine zweite Zeile, schließt sich die erste. Ein Tippen an anderer Stelle oder Scrollen schließt sie ebenfalls.
- Beide Seiten. Trailing Actions (Archivieren, Löschen) kommen bei einem Swipe nach links heraus, Leading Actions (Als gelesen markieren) bei einem Swipe nach rechts.
Apples Richtlinien zu Gesten (opens in a new tab) ergänzen den Teil, den Web-Portierungen vergessen: Menschen sollen mehr als einen Weg haben, eine App zu bedienen. Ein Swipe ist eine Abkürzung, nie der einzige Weg.
Wie baust du eine Swipe-Zeile von Grund auf selbst?
Leg die Aktionen in eine untere Ebene, den Inhalt der Zeile darüber, und treib die Zeile mit einem Motion Value an, damit kein pointermove React neu rendert. Mit drag="x" setzt Motion touch-action: pan-y auf das Element. Der Browser behält also das vertikale Scrollen und gibt dir die horizontalen Bewegungen:
Das ist eine funktionierende Zeile für Slide to Delete, aber nur ein Gerüst. Was noch fehlt: Der versteckte Button bleibt in der Tab-Reihenfolge, solange die Zeile zu ist (setz inert auf die Action-Ebene, bis sie aufgeht). Ein Swipe, der auf einem Link endet, löst dessen Klick aus. Eine zweite offene Zeile schließt die erste nicht. Es gibt kein haptisches Ticken, wenn der Full Swipe scharf wird, und keinen Weg, ohne Ziehen zu löschen. Die Flick-Geschwindigkeit von 400px/s und die Schwelle von einem Viertel der Breite entsprechen den gemeinsamen Gesten-Konstanten in lib/motion von Wingo UI. Sie sind so abgestimmt, dass sie an derselben Stelle schließen wie ein vaul-Drawer.
Wie fügst du einer React-Liste mit Wingo UI Swipe Actions hinzu?
Gib jeder Zeile ein Feld swipe mit den Arrays leading und trailing. Jede Aktion ist { id, label, icon?, tone?, color?, onSelect, destructive? }. Eine destructive-Aktion schiebt die Zeile hinaus und schließt die Lücke, und du entfernst das Element in onSelect. Ist das Element 700ms nach dem Abschluss von onSelect noch in der Liste (eine abgebrochene Bestätigung, ein fehlgeschlagener Request), gleitet die Zeile von selbst zurück.
Der Swipe folgt den fünf Verhaltensweisen von oben. Die Buttons sind 76px breit, und ihre Icons wachsen beim Herauskommen von 60 % auf volle Größe. Lass nach mehr als einem Viertel der Button-Breite los oder mach einen Flick mit 400px/s, dann rastet die Zeile offen ein. Ab 60 % der Zeile wird die äußerste Aktion scharf, überdeckt die anderen und löst ein kurzes navigator.vibrate(10) aus, wo der Browser das unterstützt; lässt du dort los, läuft sie. Abschalten kannst du das mit fullSwipe={false}. Nur eine Zeile bleibt offen, und ein Tippen auf sie, ein Tippen an anderer Stelle oder Scrollen schließt sie. Eine Maus startet standardmäßig nie einen Swipe, damit Textauswahl und Klicks auf dem Laptop normal funktionieren. Mit swipeWithMouse löst auch Ziehen mit der Maus einen Swipe aus, und so kannst du es unten ausprobieren.
$ npx wingo-ui@latest add listInputs, Textareas, Selects und alles in [data-no-swipe] starten nie einen Swipe, ein Switch im trailing-Slot einer Zeile bleibt also bedienbar. Liegt deine Liste auf einer anderen Fläche als dem Seitenhintergrund, setz die Swipe-Fläche mit className="[--list-bg:var(--card)]", damit die gleitende Zeile deckend bleibt.
Soll Swipe to Delete nachfragen oder Undo anbieten?
Biete Undo an. Swipe to Delete mit Undo schlägt eine Bestätigung bei den meisten Listen: Ein Full Swipe ist schon eine bewusste Handlung, und ein Dialog nach jedem Löschen bringt Leuten bei, ohne zu lesen auf Bestätigen zu klicken. Der Artikel der Nielsen Norman Group Confirmation Dialogs Can Prevent User Errors, If Not Overused (opens in a new tab) sagt es deutlich: Wer zu oft warnt, dem hört irgendwann niemand mehr zu. Derselbe Artikel rät Designern, keinen Aufwand zu scheuen, um Undo anzubieten.
Das Muster hat drei Schritte: Zeile entfernen, dem Server Bescheid geben und einen Toast zeigen, dessen Undo sie zurückholt. Schlägt der Request fehl, hol die Zeile zurück und zeig eine Meldung, sonst zeigt die Liste ein Löschen, das der Server nie ausgeführt hat.
Häng <Toaster /> einmal in dein Root-Layout, dann funktioniert toast() überall. Der Timer des Toasts pausiert, solange der Zeiger über dem Stapel liegt, solange der Tastaturfokus darin steht und solange der Tab im Hintergrund ist. So läuft das Undo nicht ab, während jemand es liest. Mit Alt+T springen Tastaturnutzer zum neuesten Toast. Ist deine Liste nach Datum sortiert, sortier beim Undo neu, statt den Index wiederzuverwenden, denn andere Löschvorgänge können ihn verschoben haben.
$ npx wingo-ui@latest add toastWo du das Löschen festschreibst, zählt mehr als die Animation. Die Nachricht sofort in einen Papierkorb-Ordner zu verschieben und sie bei Undo zurückzuholen (ein Soft Delete), übersteht einen geschlossenen Tab. Mit dem Server-Aufruf auf die Callbacks onAutoClose und onDismiss des Toasts zu warten, übersteht ihn nicht: Schließt jemand den Tab in diesen sechs Sekunden, kommt beim Server nichts an. Behalte eine echte Bestätigung für Löschvorgänge, die du nicht rückgängig machen kannst, etwa das Löschen eines Kontos. Unser Guide zu Slide to Confirm behandelt diesen Fall.
Wie löschen Maus- und Tastaturnutzer die Zeile?
Über dieselben Aktionen, in einem Menü. Ein Swipe ist eine Ziehbewegung, und das WCAG-2.2-Erfolgskriterium 2.5.7 (opens in a new tab) (Stufe AA) verlangt für alles, was ein Ziehen braucht, eine Alternative mit einem einzelnen Zeiger. Die List baut diese Alternative aus dem Feld swipe, du pflegst also nie zwei Listen mit Aktionen:
- Maus: Ein ⋯-Button erscheint bei Hover und Fokus, und ein Rechtsklick öffnet dasselbe Menü. Mit
actionsVisibility="always"bleibt der Button sichtbar. - Tastatur: Mit Tab auf die Zeile, dann öffnet Shift+F10 oder die Menütaste das Menü, und beim Schließen kehrt der Fokus zur Zeile zurück. Geschlossene Swipe-Buttons sind
inert, Tab landet also nie auf einem versteckten Löschen. - Touch ohne Swipe: Ein Long Press öffnet ein Bottom Sheet mit denselben Aktionen. Wer nicht ziehen kann, erreicht Löschen trotzdem mit einem Tipp.
Das Menü listet zuerst die eigenen actions der Zeile, dann jede Swipe-Aktion, mit Löschen im Danger-Ton. Auf Smartphones ist der ⋯-Button immer sichtbar, mit einer Trefferfläche von 44px, der Größe, die unser Guide zur minimalen Touch-Target-Größe empfiehlt.
Außerhalb einer Liste braucht ein Löschen trotzdem einen sichtbaren Button. Auf einer Detailseite einer Nachricht oder in einer Toolbar bestätigt der kostenlose Delete Button inline: Der Mülleimer öffnet an Ort und Stelle ein kleines Panel mit Haken und Kreuz, Escape bricht ab, und das Ergebnis wird Screenreadern angesagt. Benenne, was er löscht, wenn mehrere auf einem Screen stehen.
$ npx wingo-ui@latest add delete-buttonWelche Stolperfallen bei Swipe to Delete zeigen sich auf echten Smartphones?
Prüf diese Punkte, bevor du ein iOS Swipe to Delete im Web auslieferst, ob mit unserer Komponente oder deiner eigenen:
- Die Zurück-Geste. Safari auf iOS navigiert zurück, wenn ein Swipe am linken Bildschirmrand beginnt. Leading Actions auf einer Zeile in voller Breite konkurrieren damit, also leg die wichtigsten Aktionen auf die Trailing-Seite und denk an Apples Rat, nicht mit Gesten zu kollidieren, die System-UI aufrufen.
- Ein Klick nach dem Swipe. Eine Zeile, die auch ein Link ist, öffnet sich, wenn der Drag über ihr endet. Schluck den Klick, wenn sich der Zeiger mehr als ein paar Pixel bewegt hat (die List nimmt 8px), und behandle einen Tipp auf eine offene Zeile als Schließen.
- Zeilen, die mitten in der Animation unmounten. Entfernst du das Element, bevor das Hinausgleiten fertig ist, verschwindet die Zeile, statt hinauszugleiten. Lass die Animation zuerst laufen, wie es
animate(...).then(onDelete)oben tut, oder nimm eine Komponente, die das erledigt. - Haptik als einziges Signal. Das Ticken, wenn der Full Swipe scharf wird, ist ein Bonus. Laut caniuse (opens in a new tab) gibt es Stand Oktober 2026 kein
navigator.vibratein Safari, also muss der scharfe Zustand auch auf dem Bildschirm sichtbar sein. Unser Guide zur Web Vibration API behandelt die Lücken. - Undo, das die falsche Zeile trifft. Behalte das entfernte Objekt in der Closure des Toasts, nicht nur seinen Index, und lass den Toast lange genug stehen, um ihn zu lesen (oben 6 Sekunden).
Für alles Weitere rund um mobile UI, von Bottom Sheets bis zu Safe Areas, lies unseren Guide zu Mobile-first React-Komponenten oder stöber in Mobile UI in React. Swipe Cards sind die nächste Stufe nach Swipe-Zeilen: Das Tutorial zu React Swipe Cards baut einen Stapel im Tinder-Stil mit denselben Motion Values.
Was solltest du als Nächstes installieren?
Fang mit der Liste an, die deine App schon hat. npx wingo-ui@latest add list kopiert die List mit Swipe Actions, Menü, Long-Press-Sheet und Drag zum Umsortieren, dazu die Komponenten und Hooks, die sie importiert, und installiert ihre npm-Abhängigkeiten: motion, lucide-react, @radix-ui/react-slot und die @dnd-kit-Pakete. Die List gehört zu Wingo UI Pro, melde dich also zuerst mit npx wingo-ui@latest login an. Der Toast für das Undo ist kostenlos: npx wingo-ui@latest add toast.
Häufige Fragen
Wie funktioniert Swipe to Delete im Web wie unter iOS?
Zieh die Zeile horizontal über einen Motion Value mit touch-action: pan-y, damit vertikales Scrollen weiter funktioniert, leg Action-Buttons darunter frei, lass sie ab einem Viertel der Button-Breite oder bei einem Flick offen einrasten und führ die äußerste Aktion aus, wenn der Swipe etwa 60 % der Zeile überschreitet. Schließ jede andere offene Zeile, sobald eine neue aufgeht.
Soll Swipe to Delete eine Bestätigung abfragen?
Meistens nicht. Ein Bestätigungsdialog nach einem bewussten Full Swipe fügt jedem Löschen einen Schritt hinzu, und Leute gewöhnen sich an, ihn ungelesen wegzuklicken; die Zeile zu entfernen und ein paar Sekunden lang Undo in einem Toast anzubieten, schützt vor Fehlern, ohne jemanden auszubremsen. Behalte eine Bestätigung für Löschvorgänge, die du nicht rückgängig machen kannst.
Ist Swipe to Delete barrierefrei?
Nur mit einer Alternative. Das WCAG-2.2-Erfolgskriterium 2.5.7 Dragging Movements (Stufe AA) verlangt für alles, was ein Ziehen erfordert, einen Weg mit einem einzelnen Zeiger. Leg dieselben Aktionen also in ein Zeilenmenü oder einen sichtbaren Button, den Tastatur und Screenreader erreichen.
Wie verhindere ich, dass der Swipe mit dem vertikalen Scrollen kollidiert?
Setz touch-action: pan-y auf die Zeile, damit der Browser das vertikale Scrollen behält und horizontale Bewegungen an deinen Handler gibt, und sperr den Drag auf eine Achse, sobald er beginnt. Motion erledigt beides mit drag="x" und dragDirectionLock.
Ist die List von Wingo UI kostenlos?
Nein. Die List mit Swipe Actions ist eine Pro-Komponente. Der Toast für das Undo und der Delete Button sind kostenlos, und npx wingo-ui@latest add list kopiert die List und alles, was sie importiert, sobald du dich mit einem Pro-Konto anmeldest.
- Mobile UI
- Swipe Actions
- Gesten
- React
- Barrierefreiheit


