Swipe actions
Pre-alphaThe registry and the CLI are not published yet.Roadmap
Gestures

Swipe actions

Swipe a row sideways to reveal the actions behind it.

Draft

Specified, not implemented yet. The API is described in useSwipeActions and may change.

The user drags a row sideways. The row follows the finger and uncovers buttons underneath: archive, flag, mark as read. Released far enough, it stays open on the buttons; otherwise it closes.

Inbox
GH
Grace Hopper9:41Found the bug. It was a moth.
AT
Alan Turing9:12Re: can machines think?
MH
Margaret Hamilton8:30Priority alarms are fine
AL
Ada LovelaceYesterdayNotes on the engine, part G
DR
Dennis RitchieYesterdayhello, world
BL
Barbara LiskovMonAbout that substitution…
translateX 0 · open none · drag a row left or right

Drag a row left for Flag and Archive, or right for Read. Try a short, fast flick: speed counts as much as distance. Tap anywhere else to close the open row.

The same row, frame by frame:

closed
translateX 0

At rest. The actions wait under the row.

dragging
translateX -18

Follows the finger. Released here, it closes.

open
translateX -48

Past half, or a fast flick: snaps open.

over-drag
translateX -62

Past the actions, it resists. The buttons stretch.

leading
translateX 28

The other way reveals the leading action.

When to use it

  • Lists where the user triages many items in a row: mail, notifications, tasks, conversations.
  • Two or three frequent actions per row, at most on each side.

Swipe actions are invisible until someone tries. Keep every action reachable another way: a long press menu, the detail screen, or selection mode. Don’t use them on rows that already react to a horizontal gesture (a carousel, a slider), or in a screen that is itself swiped between tabs.

How it works

translateX is how far the row is from its rest position: negative to reveal the trailing actions, positive for the leading ones.

While dragging

RuleWhy
Direction lock after 8pt: horizontal goes to the row, vertical to the listA slightly diagonal scroll doesn’t move rows.
1:1 up to the actions’ widthThe row sticks to the finger.
Resistance past it, the buttons stretch to fill the gapThe user feels the end without a hard stop.
One open row: starting a swipe closes the other oneThe list never has two rows half-open.

Past the actions’ width w, each point counts less:

x = w + (over * resistance) / (over + resistance); // resistance: 60pt

On release

The row opens or closes from velocity first, then position:

swipe past 8ptflick close · |x| ≤ w/2flick open · |x| > w/2swipetap row, screen or actioncloseddraggingopen

The snap is a spring that starts from the release velocity, so a flick carries on smoothly instead of restarting.

Once open

  • A tap on the row, on another row or on the screen closes it without triggering anything else.
  • A tap on an action runs it and closes the row. If the action removes the row (archive, delete), the row slides out and the list closes the gap.
  • A scroll of the list closes it.

Full swipe

A side can trigger its outermost action with a long swipe, without stopping on the buttons. It suits the one action the user repeats most, often Archive or Delete.

Messages
GH
Grace Hopper9:41Taped it in the logbook.
AT
Alan Turing9:12Lunch first, then the test.
KJ
Katherine Johnson8:50Numbers check out.
LT
Linus Torvalds8:02Just a hobby.
HL
Hedy LamarrYesterdayPatent filed this morning.
TB
Tim Berners-LeeYesterdayPut it on a web page.
RP
Radia PerlmanMonThe tree converged.
Conversation deletedUndo
progress 0.00 · status idle · swipe a row far left

Swipe a row a little, then all the way. What happens after the row leaves, with the snackbar and Undo, is described in undoable action.

open
progress 0.31

Short swipe: the Delete button stays open.

armed
progress 0.73

Past 55%: haptic, the button fills the row.

DeletedUndo
removed
progress 1.00

Released: slides out, collapses, snackbar.

DeletedUndo
pending
progress 1.00

Gone from the list. Nothing sent yet.

committed
progress 1.00

Timeout: the delete is sent. Undo: the row comes back.

progress is |translateX| / rowWidth. Two thresholds split the swipe:

ZoneRelease doesFeedback
Under half the button (40pt)Closes.
Button to 55% of the rowOpens on the buttons.
Past 55% (armed)Runs the outermost action.Haptic when crossing, both ways. The button fills the row, its icon follows the row’s edge.

Crossing back under 55% before releasing disarms it: the user can change their mind until the last moment.

Only use a full swipe for an action that can be undone. Deleting something that can’t be restored (a folder with its content, an account) needs a Dialog first.

Implementation

Axiom implements it with one hook, useSwipeActions, and a SwipeableRow that renders the actions behind any Item.

LibraryUsed for
react-native-gesture-handlerGesture.Pan() with activeOffsetX([-8, 8]) and failOffsetY([-8, 8]): the direction lock, without blocking the list’s vertical scroll.
react-native-reanimatedtranslateX as a shared value, the resistance and the action widths computed in the gesture callbacks. withSpring(target, { velocity }) for the snap. progress and the armed state are derived on the UI thread; runOnJS fires the haptic when it changes.
Reanimated layout animationsexiting slides a removed row out, LinearTransition on the list closes the gap.
React contextThe id of the open row, shared by the rows of one list, so opening a row closes the other.

In a screen

Conceptual
<SwipeableList>
	<FlatList
		data={mails}
		renderItem={({ item }) => (
			<SwipeableRow
				leading={[
					{
						key: "read",
						icon: "mail",
						label: "Read",
						color: "info",
						onPress: () => toggleRead(item),
					},
				]}
				trailing={[
					{
						key: "flag",
						icon: "flag",
						label: "Flag",
						color: "warning",
						onPress: () => flag(item),
					},
					{
						key: "archive",
						icon: "archive",
						label: "Archive",
						fullSwipe: true,
						onPress: () => archive(item),
					},
				]}
			>
				<Item onPress={() => open(item)}>…</Item>
			</SwipeableRow>
		)}
	/>
</SwipeableList>

Options and return value are documented in useSwipeActions.

Accessibility

  • Expose each action as an accessibilityActions entry on the row: VoiceOver lists them in the rotor, TalkBack in its actions menu. Screen reader users never swipe rows.
  • Action buttons are hidden from screen readers while the row is closed, and focusable when it’s open.
  • Colors aren’t enough: each action has an icon and a label. The haptic isn’t the only armed signal: the button changes shape too.
  • With Reduce Motion on, the snap is a short timing instead of a spring, and a removed row disappears without sliding.