Swipe Actions
Swipeable list row that reveals leading/trailing actions, with full-swipe commit and a keyboard alternative.
|
|
| Package |
@vizejs/ui/swipe-actions |
| Maturity |
stable |
| Own the source |
vize lib pull swipe-actions |
| Requires |
context, controllable-state, id |
| Aliases |
swipe to delete, swipeable list item, leading trailing actions, row actions |
| Covers |
iOS UIContextualAction, Material swipe to dismiss, Ionic ItemSliding |
Minimal setup
Use a Vue 3.5+ project with Vue SFC compilation configured (see Vite+ integration).
vp install @vizejs/ui
Add the public import below to <script setup lang="ts">. Components provide behavior without styles; the preview uses optional Paper styles.
import { SwipeActions, SwipeActionsContent, SwipeActionsTray, SwipeActionsAction } from "@vizejs/ui/swipe-actions";
Or copy the source into your project with vize lib pull swipe-actions (see Source Distribution).
Try it
This is the basic example maintained with the component source, using the public package imports. The preview adds the optional Paper styles.
Open the interactive example · Styling these components
Copy the example
<!-- Mail list whose rows swipe to reveal archive and delete actions, with a full swipe deleting the row. -->
<script setup lang="ts">
import { ref } from "vue";
import {
SwipeActions,
SwipeActionsAction,
SwipeActionsContent,
SwipeActionsTray,
} from "@vizejs/ui/swipe-actions";
const messages = ref(["Team lunch on Friday", "Invoice #1042", "Design review notes"]);
const lastAction = ref<string | null>(null);
function run(action: string, message: string): void {
lastAction.value = `${action}: ${message}`;
messages.value = messages.value.filter((candidate) => candidate !== message);
}
</script>
<template>
<div>
<ul aria-label="Inbox">
<SwipeActions
v-for="message in messages"
:key="message"
as="li"
@full-swipe="() => run('Deleted', message)"
>
<SwipeActionsTray side="leading" aria-label="Quick actions">
<SwipeActionsAction value="archive" @select="() => run('Archived', message)">
Archive
</SwipeActionsAction>
</SwipeActionsTray>
<SwipeActionsTray side="trailing" aria-label="Destructive actions">
<SwipeActionsAction value="delete" @select="() => run('Deleted', message)">
Delete
</SwipeActionsAction>
</SwipeActionsTray>
<SwipeActionsContent>{{ message }}</SwipeActionsContent>
</SwipeActions>
</ul>
<output>{{ lastAction ?? "No actions yet" }}</output>
</div>
</template>
API
SwipeActions
Source: src/families/interaction/swipe-actions/swipe-actions.vue
Props
| Prop |
Type |
Default |
Description |
as |
PrimitiveAs |
"div" |
Element or component to render, for example "li". |
open |
SwipeActionsOpen |
undefined |
Controlled open tray (v-model:open). undefined selects uncontrolled behavior. |
defaultOpen |
SwipeActionsOpen |
null |
Initial uncontrolled open tray. |
fullSwipe |
boolean |
true |
Swiping past fullSwipeThreshold of the item width emits fullSwipe (for example delete). |
fullSwipeThreshold |
number |
0.6 |
Fraction of the item width that commits a full swipe. |
dir |
"ltr" | "rtl" |
"ltr" |
Text direction; RTL mirrors leading/trailing and arrow keys. |
disabled |
boolean |
false |
Ignore swipes and keyboard reveals. |
Events
| Event |
Payload |
Description |
update:open |
[open: SwipeActionsOpen] |
Fired when the open tray requests a change (v-model:open). |
fullSwipe |
[side: SwipeActionsSide] |
Fired when a release passes the full-swipe threshold, with the committed side. |
Slots
| Slot |
Slot props |
Description |
default |
SwipeActionsSlotState |
Renders the trays and the swipeable content with the swipe state. |
Exposed
| Member |
Type |
Description |
open |
SwipeActionsOpen |
Open tray, or null. |
offset |
number |
Current horizontal content offset in CSS px (positive moves toward inline-end). |
dragging |
boolean |
Whether a drag is active. |
fullSwipeSide |
SwipeActionsSide | null |
Tray that a release would full-swipe commit, or null. |
state |
SwipeActionsState |
Stable state token. |
openSide |
(side: SwipeActionsSide, focus?: boolean) => boolean |
Open a tray (moving focus into it when focus is true). |
close |
() => boolean |
Close any open tray. |
SwipeActionsContent
Source: src/families/interaction/swipe-actions/swipe-actions-content.vue
Props
| Prop |
Type |
Default |
Description |
keyboardHint |
string |
"Use arrow keys to reveal actions" |
Hidden description announcing the keyboard alternative to swiping. |
Slots
| Slot |
Slot props |
Description |
default |
{ readonly open: boolean } |
The visible row contents that slide to reveal actions. |
SwipeActionsTray
Source: src/families/interaction/swipe-actions/swipe-actions-tray.vue
Props
| Prop |
Type |
Default |
Description |
side (required) |
SwipeActionsSide |
required |
Edge this tray is revealed from. |
Slots
| Slot |
Slot props |
Description |
default |
{ readonly open: boolean } |
SwipeActionsAction buttons (or any focusable controls). |
SwipeActionsAction
Source: src/families/interaction/swipe-actions/swipe-actions-action.vue
Props
| Prop |
Type |
Default |
Description |
value (required) |
string |
required |
Action identifier reported by select. |
closeOnSelect |
boolean |
true |
Close the tray after the action runs. |
Events
| Event |
Payload |
Description |
select |
[value: string, nativeEvent: MouseEvent] |
Fired when the action button is activated, with its value and the native click. |
Slots
| Slot |
Slot props |
Description |
default |
Record<string, never> |
Action label or icon. |
Behavior
Normative state x input -> outcome table for swipe-actions.vue,
swipe-actions-content.vue, swipe-actions-tray.vue, and
swipe-actions-action.vue (@vizejs/ui/swipe-actions). Every row is proven by
the named test in swipe-actions.test.ts or swipe-actions-ssr.test.ts.
| # |
State |
Input |
Outcome |
Proven by |
| SW1 |
closed |
render |
focusable content with an arrow-key hint (aria-describedby, aria-keyshortcuts), inert trays (role="group"), --vize-swipe-offset: 0px |
renders a focusable row with inert trays and a keyboard hint |
| SW2 |
any |
horizontal drag |
past a 6px slop the content follows the pointer (data-state="dragging"); release opens a tray past half its width, else closes; open trays rest at their width and lose inert |
dragging reveals trays, snaps open past half the tray, and snaps back otherwise |
| SW3 |
any |
vertical drag / missing tray / cancel |
vertical gestures scroll natively, sides without trays never move, and pointercancel restores the previous position |
vertical drags and cancels never open, and sides without trays do not move |
| SW4 |
fullSwipe |
long drag |
past fullSwipeThreshold of the row width data-full-swipe names the side and release emits fullSwipe(side) then closes; disabled full swipe just opens |
a long swipe past the threshold emits fullSwipe and closes |
| SW5 |
focused content |
Arrow / Escape / action |
arrows reveal the tray toward which content moves and focus its first action; Escape or running an action closes and returns focus |
arrow keys reveal trays and focus the first action; Escape and actions close |
| SW6 |
RTL / controlled / disabled |
keys, API, drags |
RTL mirrors keys and offsets; controlled open waits for the parent; disabled rows leave the tab order and ignore input |
RTL mirrors keys and drag direction; controlled and disabled rows |
| SW7 |
no provider |
setup |
parts throw VIZE_UI_CONTEXT_MISSING: SwipeActions |
parts require a SwipeActions provider |
| SW8 |
SSR / hydration |
isolated requests |
byte-identical closed markup and hydration without diagnostics |
renders byte-identical closed rows and hydrates without mismatches |
The subpath ships no CSS.