Swipe Actions in Compose: SwipeToDismissBox Archive & Delete Patterns
Swipe-to-dismiss is a powerful UX pattern in mobile apps. Jetpack Compose provides SwipeToDismissBox to implement bidirectional swipe gestures with rich visual feedback.
SwipeToDismissBox Basics
SwipeToDismissBox allows users to swipe an item left or right to trigger actions. Each direction can have different behaviors (e.g., archive on left, delete on right).
var dismissed by remember { mutableStateOf(false) }
if (!dismissed) {
SwipeToDismissBox(
modifier = Modifier.fillMaxWidth(),
state = rememberSwipeToDismissBoxState(),
backgroundContent = {
// Background shown during swipe
},
content = {
// Your item content
},
onDismissed = { direction ->
dismissed = true
when (direction) {
EndToStart -> { /* Delete */ }
StartToEnd -> { /* Archive */ }
}
}
)
}
Directional Background Colors
Use animateColorAsState to smoothly transition background colors based on swipe direction:
SwipeToDismissBox(
modifier = Modifier.fillMaxWidth(),
state = rememberSwipeToDismissBoxState(),
backgroundContent = {
val color by animateColorAsState(
targetValue = when (state.dismissDirection) {
EndToStart -> Color.Red // Delete (right to left)
StartToEnd -> Color.Blue // Archive (left to right)
null -> Color.Gray
},
label = "swipe_bg_color"
)
Box(
modifier = Modifier
.fillMaxSize()
.background(color),
contentAlignment = Alignment.Center
) {
Icon(
imageVector = when (state.dismissDirection) {
EndToStart -> Icons.Default.Delete
StartToEnd -> Icons.Default.Archive
null -> Icons.Default.SwipeLeft
},
contentDescription = null,
tint = Color.White
)
}
},
content = {
ListItem(
headlineContent = { Text("Item Title") },
supportingContent = { Text("Swipe left to delete, right to archive") }
)
}
)
Snackbar Undo Pattern
Provide users with an undo option after dismissal:
val snackbarHostState = remember { SnackbarHostState() }
var dismissedItems by remember { mutableStateOf(emptyList<String>()) }
LaunchedEffect(dismissed) {
if (dismissed) {
val result = snackbarHostState.showSnackbar(
message = "Item archived",
actionLabel = "Undo"
)
if (result == SnackbarResult.ActionPerformed) {
dismissed = false
dismissedItems = dismissedItems.dropLast(1)
}
}
}
SnackbarHost(hostState = snackbarHostState)
Controlling Swipe Direction
Fine-tune swipe behavior with enableDismissFromStartToEnd and enableDismissFromEndToStart:
SwipeToDismissBox(
state = rememberSwipeToDismissBoxState(
positionalThreshold = 0.25f
),
enableDismissFromStartToEnd = true, // Allow swipe left → right
enableDismissFromEndToStart = true, // Allow swipe right → left
backgroundContent = { /* ... */ },
content = { /* ... */ }
)
Complete Example: Email List Item
@Composable
fun EmailListItem(
email: Email,
onArchive: () -> Unit,
onDelete: () -> Unit
) {
var dismissed by remember { mutableStateOf(false) }
val snackbarHostState = remember { SnackbarHostState() }
if (!dismissed) {
SwipeToDismissBox(
modifier = Modifier.fillMaxWidth(),
state = rememberSwipeToDismissBoxState(),
backgroundContent = {
val color by animateColorAsState(
targetValue = when (dismissState.dismissDirection) {
EndToStart -> Color(0xFFD32F2F) // Red (delete)
StartToEnd -> Color(0xFF1976D2) // Blue (archive)
null -> Color.Transparent
},
label = "email_swipe_bg"
)
Box(
modifier = Modifier
.fillMaxSize()
.background(color),
contentAlignment = Alignment.Center
) {
Icon(
imageVector = when (dismissState.dismissDirection) {
EndToStart -> Icons.Default.Delete
StartToEnd -> Icons.Default.Archive
null -> Icons.Default.SwipeLeft
},
contentDescription = null,
tint = Color.White,
modifier = Modifier.size(40.dp)
)
}
},
content = {
Card(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp)
) {
ListItem(
headlineContent = { Text(email.subject, fontWeight = FontWeight.Bold) },
supportingContent = { Text(email.preview) },
trailingContent = { Text(email.date) }
)
}
},
onDismissed = { direction ->
dismissed = true
when (direction) {
EndToStart -> {
onDelete()
// Optionally show snackbar for undo
}
StartToEnd -> {
onArchive()
}
}
}
)
}
}
Key Takeaways
- SwipeToDismissBox provides elegant swipe-dismiss UX with directional control
- Use animateColorAsState for smooth background color transitions
- Always provide undo patterns with Snackbar for destructive actions
- Control swipe directions independently with
enableDismissFromStartToEndandenableDismissFromEndToStart - Test on actual devices to ensure swipe sensitivity and responsiveness
${ CTA }
Top comments (0)