Modifier.swipeToDismiss¶
Drag past a threshold to dismiss; spring back otherwise. Horizontal or vertical.
When to use¶
- Dismissable list rows (notifications, emails, conversations).
- Toast cards, snack bars, banner alerts.
- Full-screen sheets that should swipe-to-close.
When not to use¶
- Lists where horizontal swipe is reserved for reveal-actions (use
magneticwith anchors instead). - Items inside a horizontally-scrolling parent; the gesture will fight the scroll.
Usage¶
var rows by remember { mutableStateOf(listOf("Row 1", "Row 2", "Row 3")) }
rows.forEach { row ->
Box(
modifier = Modifier
.fillMaxWidth()
.swipeToDismiss(onDismiss = { rows = rows - row })
.background(MaterialTheme.colorScheme.primary),
) {
Text(row)
}
}
Vertical dismiss for a bottom sheet:
Modifier.swipeToDismiss(onDismiss = onClose, axis = SwipeAxis.Vertical)
Parameters¶
| Name | Type | Default | Notes |
|---|---|---|---|
onDismiss | () -> Unit | required | Called once when a dismiss commits. |
threshold | Float | 0.4f | Drag distance, as fraction of receiver size, to commit. |
axis | SwipeAxis | Horizontal | Drag axis. |
Design notes¶
- Node subtype:
DelegatingNode + LayoutModifierNode. We delegate toSuspendingPointerInputModifierNodeand usedetectDragGesturesfor the gesture loop. TheAnimatable<Float>tracks the offset and is applied viaplaceable.place(x, y)inmeasure(). - Why
DelegatingNode? It's the supertype that exposes thedelegate(...)API. We can hand off pointer handling to a purpose-built suspending detector while still implementingLayoutModifierNodeourselves for placement. - Cancellation:
onCancelPointerInput(inherited via the delegate) andonDragCancelboth spring back. Threshold check uses the receiver's measured size on the drag axis, so the same fractional threshold works for any element size. - Commit animation: on commit we animate to ±
sizePx(off-screen) before callingonDismiss. That's the visual "swept away" tell; the consumer's removal happens after the animation lands.