SwipeToDismiss
MD3 swipe-to-dismiss row (Compose SwipeToDismissBox): a horizontally
draggable row over a background that is revealed as it slides away.
Typically wraps a ListItem inside a List.
In a list
Swipe a row start→end to archive or end→start to delete. Each direction
reveals its own background (startToEndBackground /
endToStartBackground); the row slides out and onDismiss then removes it.
Inside a List, SwipeToDismiss renders the <li> and the nested
ListItem renders a <div>, so list semantics are kept.
<List>
{items.map((item) => (
<SwipeToDismiss
key={item.id}
onDismiss={(direction) => remove(item.id, direction)}
startToEndBackground={<ArchiveBackground />}
endToStartBackground={<DeleteBackground />}
>
<ListItem headline={item.title} />
</SwipeToDismiss>
))}
</List>
A single background is used for both directions when the
direction-specific ones are omitted. enableStartToEnd /
enableEndToStart turn a direction off, and threshold (default 56px) is
the distance a slow release must pass to dismiss — a fling of at least
125px/s dismisses regardless of distance. Directions are logical, so they
mirror in RTL.
Delete button alternative
Swiping must not be the only way to reach an action. The controlled
dismissed prop dismisses programmatically: here each row's delete button
sets it, which plays the same exit animation as a swipe; onDismiss then
removes the row and moves focus to the next row's button so keyboard users
are not dropped to <body>.
const [dismissed, setDismissed] = useState<SwipeDismissDirection | null>(null)
<SwipeToDismiss
dismissed={dismissed}
onDismissedChange={setDismissed}
onDismiss={() => removeMessage(id)}
enableStartToEnd={false}
endToStartBackground={<DeleteBackground />}
>
<ListItem
headline="Message"
trailing={
<IconButton
icon={<Delete />}
aria-label="Delete message"
onClick={() => setDismissed('endToStart')}
/>
}
/>
</SwipeToDismiss>
Set dismissed back to null to reset the row (for example to undo, or to
veto a swipe: in controlled mode, keeping the value null in
onDismissedChange cancels the dismissal). Gestures are disabled while a
row is dismissed.
Styling the background by direction
The root exposes styling hooks for backgrounds that react to the swipe:
data-direction (current swipe / dismiss direction), data-target (the
anchor the row would settle at if released now: settled / startToEnd /
endToStart), data-dismissed, data-dragging, and the
--md-swipe-to-dismiss-progress custom property (0–1, swiped distance /
row width).
.row[data-target='endToStart'] .deleteIcon {
transform: scale(1.2);
}
.row .background {
opacity: calc(0.4 + var(--md-swipe-to-dismiss-progress) * 0.6);
}
Accessibility
- Always provide a non-gesture alternative for the swipe action (a delete
button, a menu item) that sets
dismissed— m3 requires swipeable list items to expose their hidden actions another way. - Dragging starts only after an 8px touch slop, so nested buttons and clickable rows keep their clicks.
- The background is decorative (
aria-hidden); put the action's label in the alternative control. onDismissfires after the exit animation (immediately under reduced motion). If focus was inside the removed row, move it somewhere sensible, as in the delete-button demo.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
background | ReactNode | — | Content revealed behind the row, for either direction (fallback). |
defaultDismissed | SwipeDismissDirection | null | null | Initial dismissed state when uncontrolled. |
dismissed | SwipeDismissDirection | null | — | Controlled dismissed state: `null` = settled (row in place), a direction = dismissed that way. Changing it animates the row — set a direction to dismiss programmatically (e.g. from a delete button, the non-gesture alternative m3 requires), set `null` to reset / cancel a dismissal. |
enableEndToStart | boolean | true | Allow end→start dismissal. |
enableStartToEnd | boolean | true | Allow start→end (reading-direction) dismissal. |
endToStartBackground | ReactNode | — | Content revealed while swiping end→start (overrides `background`). |
onDismiss | ((direction: SwipeDismissDirection) => void) | — | Fires once the row has finished sliding out and settled in the dismissed position (immediately under reduced motion) — the place to remove the item. Also fires after a programmatic dismiss (`dismissed`). |
onDismissedChange | ((dismissed: SwipeDismissDirection | null) => void) | — | Fires when a swipe is released into a dismiss anchor (at release, before the exit animation). In controlled mode, keep the value `null` to veto. |
startToEndBackground | ReactNode | — | Content revealed while swiping start→end (overrides `background`). |
threshold | number | 56 | Positional threshold (px) a slow release must pass, in the direction of motion, to dismiss (Compose `positionalThreshold` 56dp). |