Swipe to Delete List — Touch Gesture HTML CSS JS Snippet

Swipe to Delete List · Layouts · Plain HTML, CSS & JS · Live preview

Share & Support

What's included

Features

Unified touch + mouse drag handling — works on mobile and desktop
Threshold-based commit (80px) with snap-back below threshold
Red delete reveal background with opacity proportional to swipe distance
max-height collapse animation for smooth item removal
Unread dot system with live badge count updates
Click-to-delete X button alongside swipe gesture
Empty state when all items are dismissed
no-transition during drag prevents rubber-band lag

About this UI Snippet

Swipe-to-Delete List — Touch & Mouse Drag, Threshold Delete & Collapse Animation

Screenshot of the Swipe to Delete List snippet rendered live

Swipe-to-delete is the dominant gesture pattern for list management on mobile — popularised by iOS Mail and Android Gmail, it lets users dismiss items with a natural leftward swipe without needing a separate edit mode or confirmation dialog. This snippet implements a full swipe-to-delete notification list that works on both touch (mobile) and mouse (desktop drag), with a threshold-based delete trigger, smooth height collapse animation, and an unread count badge.

Pointer event architecture: unified touch + mouse

The gesture handler listens for both touchstart/touchmove/touchend and mousedown/mousemove/mouseup. This single-handler approach avoids PointerEvents API (which has quirks with passive listeners on iOS) while covering all input types. The key trick: mousemove and mouseup listeners are attached to document on drag start (not on the element), so the drag continues even if the cursor leaves the list item during fast movement.

Threshold detection and snap behaviour

The reveal distance is tracked in currentX (clamped to Math.min(0, x) — left-only swipe). When pointerUp fires, if Math.abs(currentX) >= THRESHOLD (80px), the item is committed to deletion: the content is animated to translateX(-100%) then deleteItem() fires after 250ms. If below threshold, the item snaps back to translateX(0). The delete background opacity is driven by progress: Math.min(1, Math.abs(currentX) / THRESHOLD), so it fades in proportionally to swipe distance.

Height collapse animation

When an item is deleted, the .removing class is added which sets max-height: 0 and opacity: 0 (with CSS transitions). After 380ms (matching the transition duration), li.remove() is called to clean up the DOM. This max-height collapse technique is a CSS-only approach to height transitions — animating height from a computed value is impossible, but animating from a known max down to 0 works in all browsers.

Unread dot and badge

Items with unread notifications have a .item-dot.unread element (a small blue circle). After each deletion, updateBadge() counts remaining .unread dots and updates the badge number. If count reaches 0, the badge is hidden. This is a realistic implementation pattern for notification UIs.

`no-transition` class during drag

During active drag, the .no-transition class is added to the swipe track, which sets transition: none on .item-content. Without this, every pixel of drag movement would trigger a CSS transition, causing a laggy "rubber band" effect. The transition is only re-enabled on pointer release, so the snap-back and delete animations run smoothly.

React integration

In React, maintain items array in state. Each item has an id and removing boolean. Swipe handlers update a swipeOffsets ref (not state, to avoid re-renders during drag). On pointerUp, check threshold and call setItems(prev => prev.filter(i => i.id !== id)). Use useRef for the drag state. Apply className={removing ? 'removing' : ''} on the list item. Use a useEffect cleanup to remove document event listeners on unmount.

See also the checkout form snippet for form step UX patterns, the progress wizard snippet for multi-step flows, and the floating chat widget snippet for notification-style overlays.

Build with AI

Build, Understand, Optimize, and Extend It With AI

You don't have to trace the drag-to-delete math by hand. Paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to explain exactly why currentX is clamped with Math.min(0, x) to allow only leftward movement, and how the no-transition class prevents the rubber-band lag that would otherwise appear on every pixel of drag. The same assistant can help optimize it — for instance whether attaching mousemove and mouseup to document on every single pointerDown call (rather than once globally) scales well with a long list, or whether the max-height collapse animation's fixed 80px value should instead be measured from each item's real rendered height. It's also useful for extending the list: ask it to add a swipe-right "archive" action alongside delete, show an undo snackbar for a few seconds before actually removing an item, or persist the remaining notifications to storage. Treat the code less like a finished artifact and more like a starting point for a conversation.

Prompt to recreate it

Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:

text
Build a "swipe to delete" notification list in plain HTML, CSS, and JavaScript using unified mouse and touch drag handling — no gesture library, no framework.

Requirements:
- A list of items, each containing a red delete-background layer positioned absolutely behind the row and a foreground content layer (avatar, title, timestamp) that sits on top and gets dragged.
- Track horizontal drag distance from a pointerdown/touchstart start position, clamping the value so only leftward movement (negative offsets) has any effect — rightward drags must do nothing.
- While dragging, translateX the foreground content by the clamped drag distance in real time, and fade in the delete background's opacity proportional to how far the drag has progressed toward a fixed pixel threshold (e.g. 80px).
- During an active drag, disable the CSS transition on the foreground content (add a "no transition" state) so the row follows the pointer with zero lag, then re-enable the transition the moment the drag ends so the snap-back or delete-commit animation is smooth.
- On release, if the drag distance met or exceeded the threshold, animate the content fully off-screen to the left and then remove the item; if it fell short, animate the content back to its resting position and fade the delete background back to invisible.
- Deleting an item (whether via swipe-commit or a separate always-visible small delete button) must first collapse the row's max-height to zero with a CSS transition and fade its opacity out, and only remove the element from the DOM after that collapse transition finishes.
- Maintain an unread-count badge at the top of the list that recalculates by counting a specific unread-indicator class among remaining items every time an item is deleted, hiding the badge entirely when the count reaches zero, and show a distinct empty state once every item has been removed.

Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.

Step by step

How to Use

  1. 1
    Copy all three blocksPaste the HTML, CSS, and JS. Five notification items render in a card panel with gradient avatars and timestamps.
  2. 2
    Try swipe on mobileOn a touch device, swipe any item left. A red delete background reveals beneath. Swipe past 80px to trigger deletion, or release early to snap back.
  3. 3
    Try drag on desktopClick and drag an item to the left with your mouse. The same threshold logic applies — drag past 80px to delete.
  4. 4
    Use the X buttonEach item has a small X button on the right for click-to-delete without swiping — useful for mouse-first users.
  5. 5
    Watch the badge updateThe red notification badge counts unread items (those with blue dots). It decrements as unread items are deleted.
  6. 6
    Dismiss all itemsDelete all five items to reveal the empty state — a grey icon and "All caught up!" message.

Real-world uses

Common Use Cases

Notification Panels
Swipeable notification list in mobile apps and web dashboards
Task and To-do Lists
Swipe-to-complete or swipe-to-dismiss gesture for task management UIs
Email and Inbox UIs
Gmail-style swipe to archive or delete email list items
Chat Message Lists
Swipe to delete messages in a chat UI — common pattern in messaging apps

Got questions?

Frequently Asked Questions

Extend the handler to track positive currentX values. Add an archive-bg div on the left side. When swipe right exceeds threshold, trigger archive instead of delete. Use green for archive, red for delete.

On delete, instead of immediately removing the item, mark it as removed in state but keep it in the DOM for 5 seconds. Show a "Undo" snackbar. If undo clicked, un-mark it. After 5s with no undo, call the actual delete API.

Use useRef for drag state (not useState — avoid re-renders during drag). Attach event listeners via useEffect. Store item removal in useState with a removing flag for the CSS animation, then filter the item from state after the animation duration.

Call e.preventDefault() inside touchmove only when horizontal movement exceeds ~5px (to distinguish horizontal swipe from vertical scroll). Check Math.abs(deltaX) > Math.abs(deltaY) before starting the swipe gesture.

Open the Export menu (or the Test Exports preview) in the snippet toolbar. It generates a plain React component, a React + Tailwind version where the row and delete-background styles become utility classes, a Vue 3 single-file component, and an Angular standalone component. Each converter preserves the markup, the collapse animation, and the touch/mouse drag behaviour, so the gesture works identically across React, Vue, and Angular. In React, keep the live drag offset in a useRef so dragging does not trigger re-renders, attach the pointer listeners in useEffect, and only move item removal into useState when you run the collapse animation — the same pattern maps to onMounted in Vue and ngAfterViewInit in Angular.

Swipe gestures are not keyboard accessible on their own, so reveal a real delete button on each row when it receives focus and wire it to the same delete function. Give each row role="listitem" inside a role="list" container, label the delete control with aria-label, and announce removals through an aria-live="polite" region so screen-reader users hear "Notification deleted" when an item disappears.