Undoable action
An action that happens right away and can be cancelled for a few seconds.
Draft
Specified, not implemented yet. It relies on Snackbar and
its onDismiss reason. The API may change.
Instead of asking “Are you sure?”, the app does what the user asked and gives them a few seconds to take it back. Common actions stay fast, and a mistake costs one tap.
Tap the archive button on a notification. The row leaves right away, but the footer shows that nothing reached the server yet. Tap Undo and the row comes back in its place. Let the snackbar run out, archive a second row, or press Leave screen, and the request leaves.
The same action, frame by frame:
Four rows. The user taps archive on the second.
The row is gone, Undo is offered. Nothing sent yet.
Undo: the row is back in its place.
No undo within 4s: the request leaves.
When to use it
- Frequent actions on items the user owns: messages, notifications, cart items, saved searches.
- Actions whose server call can wait a few seconds.
It works whatever triggers the action: a button in the row, a full swipe, a menu entry, a toolbar action.
Don’t use it when the action can’t be reverted, or affects more than the user sees, like a folder with its content or an account. Ask first with a Dialog.
How it works
The screen updates immediately. The request waits for the snackbar to close.
- Optimistic update. The list changes right away, so the user can keep going.
- One pending action. A new action replaces the snackbar, which commits the previous one.
- Commit on leave. Leaving the screen or backgrounding the app commits what’s pending.
- Undo restores in place. The item comes back at its position, not at the end of the list.
- If the request fails, put the item back and show the error in a Toast.
Implementation
In a screen
const [removed, setRemoved] = useState<string[]>([]);
const visible = messages.filter((m) => !removed.includes(m.id));
const remove = (message: Message) => {
setRemoved((ids) => [...ids, message.id]);
snackbar.show({
message: "Conversation deleted",
duration: "short",
action: {
label: "Undo",
onPress: () =>
setRemoved((ids) => ids.filter((id) => id !== message.id)),
},
// 'timeout', 'swipe' and 'replaced' (the next action) all commit.
onDismiss: (reason) =>
reason !== "action" && api.deleteMessage(message.id),
});
};Accessibility
- The snackbar is announced and keeps its action focusable for its whole duration.
- Extend the snackbar’s duration when a screen reader is on.
- With Reduce Motion on, the item disappears and comes back without animation.