Swipe Delete Row

Makes swipe-to-delete deliberate: the row deletes only past a visible line, never on a flick or scroll, and keeps an Undo.

StopsSwipe while scrolling deleted row

Installation

pnpm dlx shadcn@latest add https://bollard-ui.vercel.app/r/swipe-delete-row.json

The files land in your project. They're yours to edit.

Usage

swipe-delete-row-usage.tsx
import { SwipeDeleteRow } from "@/components/ui/swipe-delete-row"

<SwipeDeleteRow
  key={row.id}
  itemLabel={`Invoice #${row.id}`}
  onDelete={() => setRows((current) => current.filter((item) => item.id !== row.id))}
>
  <div className="flex items-baseline justify-between gap-3">
    <p className="truncate text-sm font-medium">{row.client}</p>
    <p className="shrink-0 text-sm tabular-nums">{row.amount}</p>
  </div>
  <p className="text-xs text-muted-foreground">
    #{row.id} · {row.due}
  </p>
</SwipeDeleteRow>

What it catches

  • Swipes that don't cross the threshold
  • Fast flicks that cross it too quickly (minSwipeMs)
  • Vertical scrolls mistaken for swipes
  • Deletes regretted within undoFor

Props

SwipeDeleteRow props
PropTypeDefault
itemLabel* (required)

Name of the thing in the row, used in labels: "Invoice #1042".

string—
onDelete* (required)

Fires once the undo window closes (or the row unmounts while it's open). Remove the item here.

() => void—
onUndo

Fires when the user undoes a delete.

() => void—
threshold

How far (0 to 1 of the row's width) a swipe must travel before release deletes.

number0.4
minSwipeMs

Swipes that cross the line faster than this (ms from touch-down) are treated as accidental flicks.

number160
undoFor

How long Undo stays available, in milliseconds.

number5000
deleteLabel

Templates: `{item}` is filled in.

string"Delete {item}"
releaseText

Shown when releasing will delete.

string"Release to delete"
deletedText

Shown after the row is deleted.

string"Deleted {item}"
undoLabel

Label of the Undo button.

string"Undo"
tooFastText

Shown when a flick was too fast to count.

string"Too quick. Drag past the line to delete."

* required. Also accepts the native props of <div> (className, aria-*, data-*…), passed through to it.

Accessibility

  • Trash button with an aria-label naming the item, for keyboard and switch users
  • After a keyboard delete, focus moves to Undo; after undo, back to the trash button
  • Status uses role=status and a polite live region
  • Spring motion respects reduced motion

Only what the source does. Test with your own screen reader and content before shipping.

Limitations

  • onDelete runs after the undo window, so the item stays in your data until then
  • Undo is in-memory; a navigation commits the delete

What it stops

Swipe while scrolling deleted row

Also known as

swipe to delete · swipe actions · list row · mobile list · inbox · gesture · ios style

Dependencies

Pairs well with