Undoable
A list item or table row that collapses in place to a label and Undo when it's removed.
- q3-report.pdf
- notes.md
// Nothing has run yet
npx shadcn@latest add @sureui/undoable
import { Button } from "@/components/ui/button"import { Undoable } from "@/components/ui/sureui/undoable"<Undoable render={<li />} label={`Deleted ${file.name}`} onConfirm={() => deleteFile(file.id)}> {({ remove }) => ( <> {file.name} <Button onClick={remove}>Delete</Button> </> )}</Undoable>remove collapses the row to label and Undo, and onConfirm runs when the window ends.
Table rows
Pass render={<TableRow />} to wrap a table row. The label and Undo go in one cell that spans every column.
| feature/billing-v2 | |
| fix/login-redirect |
// Nothing has run yet
Undo duration
The window lasts 5 seconds, or the milliseconds you pass to undo between 4 and 60 seconds.
- Maya Chen
- Leo Brandt
// Nothing has run yet
With undo="manual", Undo stays until people move on: a press outside the row or focus moving elsewhere runs onConfirm.
Focus after removal
If focus was in the row when onConfirm finishes, it moves to the same control in the next row, else the previous row, else the list, which gets tabindex="-1". Rows still showing Undo are skipped. focusAfterRemove receives the row and returns the element to focus instead, or null to leave focus alone. Try it with the keyboard in the first example.
Errors
If onConfirm throws or rejects, the row comes back. Pass onConfirmError to receive the error instead of having it rethrown.
- q3-report.pdf
- notes.md
// Nothing has run yet
Undoable
The table lists the props it adds to the element it renders.
| Prop | Type | Default |
|---|---|---|
| ...elementProps | props of the render element | — |
| onConfirm | () => void | Promise<unknown> | required |
| onCancel | () => void | — |
| onConfirmError | (error: unknown) => void | — |
| children | ReactNode | (({ remove, state }) => ReactNode) | — |
| render | ReactElement | <div /> |
| label | ReactNode | "Deleted" |
| undoLabel | ReactNode | "Undo" |
| undo | boolean | number | "manual" | true |
| pauseUndoOnHover | boolean | true |
| pauseUndoOnFocus | boolean | true |
| focusAfterRemove | (row: HTMLElement) => HTMLElement | null | next row, previous row, then the list |
| announcements | { undo? } | — |
Data attributes
The rendered element sets data-state. Drop the item from your data once it's removed. After onConfirm fails, it also sets data-error until the next removal.
| Value | When |
|---|---|
| idle | Showing its children. |
| undo | Collapsed, with the undo window open. |
| pending | onConfirm returned a promise that hasn't settled. |
| removed | onConfirm has run. |
- If focus was in the row, it moves to Undo, and back to the button that removed the row after Undo.
- Once the row is removed, focus moves to a neighboring row or the list, so it isn't lost to the page.
- The label and "Undo is available." are announced in a polite live region. Change the text with
announcements. - With
prefers-reduced-motion, the fill on Undo doesn't move.
WCAG
- 2.2.1 Timing Adjustable:
undo="manual"meets it, since Undo has no time limit. A numericundostops at 60 seconds.