motif svelteArc

Swipe actions ported

Reveal row actions with a swipe, or from the same actions in a menu.

A list whose rows reveal actions on a horizontal swipe, the way a mobile mail client does. Every row also includes an accessible More Actions menu powered by Bits UI DropdownMenu.

Live specimen

Loading demo…

Guidance

When to use

  • Touch-first triage lists such as mail, notifications, or tasks.
  • Rows with one or two quick actions per side, like Archive and Mark unread.
  • Lists where swipe gestures streamline high-volume item processing.

When not to use

  • Use context-menu or dropdown-menu for desktop-first data tables.
  • Use reorderable-list when rows need to be dragged vertically.
  • Use hold-to-confirm for a single destructive action outside a list.

Installation

pnpm dlx shadcn-svelte@latest add @arcui/swipe-actions

Usage

<script lang="ts">
  import { SwipeActions, SwipeActionsRow } from '$lib/components/swipe-actions';
  import { Archive, MailOpen } from '@lucide/svelte';

  let messages = $state([
    { id: '1', sender: 'GitHub', subject: 'New release v2.4.0 published', time: '10:42 AM' }
  ]);

  function archive(id: string) {
    messages = messages.filter(m => m.id !== id);
  }
</script>

<SwipeActions label="Inbox">
  {#each messages as message (message.id)}
    <SwipeActionsRow
      label={message.subject}
      leading={[
        { label: 'Unread', icon: unreadIcon, tone: 'accent', keepRow: true, onSelect: () => {} }
      ]}
      trailing={[
        { label: 'Archive', icon: archiveIcon, tone: 'danger', onSelect: () => archive(message.id) }
      ]}
    >
      <div class="message-preview">
        <strong>{message.sender}</strong>
        <span>{message.subject}</span>
      </div>
    </SwipeActionsRow>
  {/each}
</SwipeActions>

Variants & Examples

Dual Actions (Done & Archive)

Left swipe reveals Done (accent), right swipe reveals Archive (danger).

<SwipeActionsRow
  label="Deploy update"
  leading={[{ label: 'Done', tone: 'accent', onSelect: complete }]}
  trailing={[{ label: 'Archive', tone: 'danger', onSelect: archive }]}
>
  <RowItem />
</SwipeActionsRow>

Keep Row After Action

Set keepRow: true for actions that toggle state without removing the row (such as Mark as Read).

<SwipeActionsRow
  label="Message"
  leading={[{ label: 'Unread', tone: 'accent', keepRow: true, onSelect: toggleUnread }]}
>
  <MessageItem />
</SwipeActionsRow>

API Reference

PropTypeDefaultDescription
labelstring–Accessible name for the list, applied via aria-label on the root ul element.
childrenSnippet–SwipeActionsRow elements composing the list.
leadingSwipeAction[][]Actions placed under the left edge, revealed by swiping right.
trailingSwipeAction[][]Actions placed under the right edge, revealed by swiping left.
fullSwipebooleantrueAllows an extended swipe or flick to trigger the outermost action directly.
classstring–Additional CSS class applied to the list container or row.

Keyboard Interactions

KeyAction
Enter / SpaceOpens the row More Actions menu from its trigger button.
ArrowDown / ArrowUpNavigates between actions in the dropdown menu.
EscapeCloses the dropdown menu or resets any open swipe offset.

Accessibility

  • Semantic markup: list container is a ul with role="list" and aria-label; each row is an li.
  • Every row includes an accessible "More actions for <label>" menu containing all actions.
  • Revealed gesture buttons are aria-hidden and tab-index="-1", ensuring screen readers and keyboard users use the accessible dropdown.
  • Focus is managed appropriately on item removal.

Motion

Phase 1 still-state: Row reveals leading/trailing action layers on horizontal pointer drag with 76px action snap points; dropdown menu features standard enter/exit animations. Phase 2 wires gesture velocity projection, rubber-band resistance beyond last stop, animated full-swipe stretch, and height collapse on row deletion.

Notes for AI

  • Use for touch-first triage lists on mobile/touch interfaces.
  • Every row requires a descriptive label for the accessible More Actions button.
  • Handle state removal in onSelect; set keepRow: true if the item stays in the list.

Source of truth: registry/components/swipe-actions/swipe-actions.tsx