Skip to content
WANCRAFT

B.187Cards & Containers

Swipe Actions

List rows that reveal actions when swiped, with full-swipe triggers and an accessible Actions menu on every row.

NewReactMotionTailwindadvanced

Overview

Swipe Actions is the mobile mail pattern for any list. Swipe a row right to reveal its leading actions or left for its trailing ones; the revealed buttons share the uncovered space and stay tappable, and past a threshold the outermost action takes over the whole strip with a springy icon bump, so letting go runs it — archive, delete, flag. Rows rubber-band past their limits, tell vertical scrolls apart from horizontal swipes, swallow the click that ends a drag, and spring back. Wrapped in SwipeActionsGroup only one row is open at a time and announcements outlive rows that get removed. Nothing depends on swiping: each row has an Actions menu button with the full list, keyboard navigable, and destructive actions can ask for an inline confirmation that takes focus to Cancel and closes with Escape.

01Installation

Method 01 · Registry

CLI verified

Uses the shadcn CLI in a project initialised with shadcn init. Installs the file to components/ui/swipe-actions.tsx and adds its dependencies.

npx shadcn@latest add https://wancraft.my/r/swipe-actions.json

Method 02 · Copy and paste

  1. 1. Install the dependencies below.
  2. 2. Copy the source from the Code tab into components/ui/swipe-actions.tsx.
  3. 3. Import it and adjust props.
npm install motion
Dependencies
motion
Peer
react ≥ 18, tailwindcss ≥ 4
Added
13 Oct 2026
Updated
—

02Usage

The default configuration. Change properties above to generate a tailored example in the Usage tab.

example.tsx
import { SwipeActions, SwipeActionsGroup } from "@/components/ui/swipe-actions";

export function SwipeActionsExample() {
  const messages = [
    { id: "1", from: "Ana Ruiz", subject: "Studio walkthrough" },
    { id: "2", from: "Kenji Mori", subject: "Revised quote" },
  ];

  return (
    <SwipeActionsGroup label="Inbox" className="divide-y overflow-hidden rounded-xl border">
      {messages.map((m) => (
      <SwipeActions
        key={m.id}
        label={m.from}
        leadingActions={[{ id: "archive", label: "Archive", color: "#24855C", dismiss: true, onSelect: () => console.log("archive", m.id) }]}
        trailingActions={[{ id: "delete", label: "Delete", color: "#D93D42", destructive: true, dismiss: true, confirm: "Delete this message?", onSelect: () => console.log("delete", m.id) }]}
      >
        <div className="px-4 py-3">
          <p className="font-medium">{m.from}</p>
          <p className="text-sm text-zinc-500">{m.subject}</p>
        </div>
      </SwipeActions>
      ))}
    </SwipeActionsGroup>
  );
}

03Props

PropTypeDefaultDescription
childrenReactNode—Row content (it slides with the swipe).
leadingActionsSwipeAction[][]Revealed by a right swipe; the first is the full-swipe action.
trailingActionsSwipeAction[][]Revealed by a left swipe; the last is the full-swipe action.
labelstring—Short row name for the menu button: "Actions for Ana Ruiz".
fullSwipebooleantrueSwiping past the threshold runs the outermost action on release.
fullSwipeThresholdnumber0.5Threshold as a fraction of the row width (never less than the open width + 24 px).
actionWidthnumber76Width of each revealed action in px.
showLabelsbooleantrueLabels under the action icons (otherwise screen-reader only).
spring{ stiffness: number; damping: number; mass: number }{ stiffness: 420, damping: 34, mass: 0.9 }Snap open / closed / back spring.
accentstring"#FF7448"Focus ring and menu highlight colour.
contentClassNamestring"bg-white dark:bg-[#17191c]"Classes for the sliding surface; it needs an opaque background.
disabledbooleanfalseDisables swiping and the menu.
SwipeAction{ id; label; icon?; color?; textColor?; onSelect?; dismiss?; confirm?; confirmLabel?; destructive?; announcement? }—dismiss slides the row out before onSelect; confirm asks inline first.
SwipeActionsGroup{ label?: string } & HTMLAttributes<HTMLDivElement>—List wrapper: one open row at a time and a shared live region.

04Features

  • 01Leading and trailing actions with icons, colours and labels that share the revealed space
  • 02Full swipe past a threshold runs the outermost action, with a springy icon bump as it arms
  • 03Velocity-aware snapping, rubber-band past limits, and spring-back
  • 04Horizontal / vertical intent detection: vertical drags scroll the page as usual
  • 05SwipeActionsGroup keeps one row open at a time; tap elsewhere or Escape closes it
  • 06Dismissing actions slide the row out before onSelect; optional inline confirmation
motion · springinput · draginput · clickinput · keyboardinput · focus

05Accessibility

  • Every row has an "Actions for …" menu button (aria-haspopup="menu", aria-expanded): Enter, Space or ArrowDown open it; arrows, Home and End move; Enter runs; Escape or Tab closes and returns focus.
  • Revealed swipe buttons are inert until their side is open, so keyboard and screen-reader users get one clean path through the menu.
  • Confirmations are a labelled group that takes focus on Cancel; Escape cancels only the confirmation and focus returns to the menu button.
  • When a dismissed row had focus, focus moves to the neighbouring row's menu button.
  • A polite live region (shared by the group) announces each action, e.g. "Archived".
  • Reduced motion: snaps and dismissals happen instantly; there is no icon bump.

06Limitations

  • Rows need an opaque background (contentClassName) so the actions stay hidden until swiped.