Skip to content

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

tsx
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.

PropTypeDefault
...elementPropsprops of the render element—
onConfirm() => void | Promise<unknown>required
onCancel() => void—
onConfirmError(error: unknown) => void—
childrenReactNode | (({ remove, state }) => ReactNode)—
renderReactElement<div />
labelReactNode"Deleted"
undoLabelReactNode"Undo"
undoboolean | number | "manual"true
pauseUndoOnHoverbooleantrue
pauseUndoOnFocusbooleantrue
focusAfterRemove(row: HTMLElement) => HTMLElement | nullnext 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.

ValueWhen
idleShowing its children.
undoCollapsed, with the undo window open.
pendingonConfirm returned a promise that hasn't settled.
removedonConfirm 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 numeric undo stops at 60 seconds.