Undo Commit Toast
An undo window that defers the action instead of reversing it, and pauses when you tab toward the button.
- Category
- Patterns
- Added
- 2026-09-21
- Deps
- none
- Updated
- 2026-09-21
This is a viberdy Pro component
The live preview, the props panel and the write-up are open to everyone. The full source, the AI prompt and CLI install are part of Pro.
- Q4 pricing deck.key
- Warehouse audit — final.pdf
- Brand marks (archive).zip
- Supplier list 2026.csv
- remove a row, then tab to Undo
Props
"use client";
import { useEffect, useRef, useState } from "react";
type Pending<T> = {
id: number;
item: T;
index: number;
/** Absolute time the commit is due. Null while paused. */
deadline: number | null;
remaining: number;
};
export function UndoCommitToast<T extends { id: string; label: string }>({
items: initial,
undoWindow = 6000,
pauseOnHover = true,
onCommit,
// ---------------------------------------------------------------
// Full source available with a viberdy Pro subscription.
// https://viberdy.dev/pricing
// ---------------------------------------------------------------
}About this pattern
Undo is usually built backwards: the destructive thing happens straight away and the undo button tries to put it back. That works for a local array and falls apart for anything real, because reversal cannot restore a server-generated id, a created timestamp or a position in an ordered list, cannot re-establish relations that were cascaded away, and cannot unsend whatever the action triggered. The correct shape is to DEFER — hide the row immediately so the interface still feels instant, and hold the actual work until the window expires. Nothing needs undoing because nothing has happened. Three details make the window itself behave. The countdown is an absolute deadline paired with a remaining duration rather than a single timeout, because a timeout cannot be paused and resumed at the exact point it stopped. The ring is animated by writing its dash offset straight to the element, since a state update per frame re-renders the whole list sitting behind the toast. And the timer pauses on FOCUS as well as hover, which is the detail almost everyone misses and is a plain accessibility failure when it is missing: a keyboard user tabbing toward the button cannot reach it inside a six-second window. Pending commits are also flushed on unmount, or someone removes a row, navigates away, and watches it silently return.
Curator’s note
Pause the countdown on focus, not only on hover. A keyboard user cannot reach the Undo button inside six seconds otherwise.
Pairs well with
Matched on shared tags and category — the entries most likely to be used alongside this one.
Form Error Summary
patternsThe error pattern that actually works with a screen reader: focus the summary, link to the fields, punish late.
Async Action Button
componentsA button that survives a real round trip — a minimum pending duration, a width that never twitches, aria-disabled instead of disabled.
Native Sheet Dialog
patternsA sheet on the native dialog element — no hand-rolled focus trap, and the three things the platform leaves you fixed.