Xzero UI
Pro library

Undo toastPro

Archive or delete rows with a second chance: the gap closes, a countdown toast merges repeat actions, and Undo or \u2318Z puts every row back exactly where it was.

$ pnpm dlx shadcn@4.23.0 add @xzero-ui-pro/undo-toast
Live · keyboard ready

When to use

When not to use

Installation

Register both Xzero UI namespaces in your project's components.json:

{
  "registries": {
    "@xzero-ui": "https://mcp.xzero.co.uk/r/{name}.json",
    "@xzero-ui-pro": "https://mcp.xzero.co.uk/r/pro/{name}.json"
  }
}
npx shadcn@4.23.0 add @xzero-ui-pro/undo-toast

Install xzero-foundation once, and import @/components/xzero/foundation.css in your app root. No Xzero token is used by this snapshot. For sample portraits, photos, fonts, or logos, download https://mcp.xzero.co.uk/install-assets.mjs and run node xzero-install-assets.mjs <receiving-project>.

Usage

import { Archive, Trash2 } from "lucide-react";
import { UndoToast, UndoToastRow, UndoToastRowAction } from "@/components/xzero/undo-toast/undo-toast";

export function Inbox({ threads, setThreads }: { threads: Thread[]; setThreads: (t: Thread[]) => void }) {
  return (
    <UndoToast
      label="Conversations"
      items={threads}
      onItemsChange={setThreads}
      getKey={thread => thread.id}
      noun={["conversation", "conversations"]}
      onCommit={entries => api.apply(entries.map(e => ({ id: e.key, action: e.kind })))}
      style={{ paddingBottom: 76 }}
      renderItem={(thread, { remove }) => (
        <UndoToastRow
          label={`${thread.from}, ${thread.subject}`}
          leading={<Avatar src={thread.avatar} />}
          title={thread.from}
          description={thread.subject}
          meta={thread.time}
          shortcuts={{ e: () => remove("archive"), Backspace: () => remove("delete") }}
          actions={<>
            <UndoToastRowAction label="Archive" icon={<Archive size={16} />} onClick={() => remove("archive")} />
            <UndoToastRowAction label="Delete" icon={<Trash2 size={16} />} onClick={() => remove("delete")} />
          </>}
        />
      )}
    />
  );
}

API reference

UndoToast

A list whose rows can be archived or deleted with a second chance. The removed row fades and slips aside while its gap closes on a spring, and a toast rises from the bottom with a countdown ring that drains around the seconds left. Removing more rows merges them into the same toast: the count rolls up, the ring refills and the toast gives a small nudge. Undo, or Cmd/Ctrl+Z, opens every row again in its exact original place, top to bottom, with a brief accent glow, and the toast turns into a check with the restored count. Hovering or focusing the toast pauses the countdown. When it runs out the batch is committed; if the commit fails the rows come back and the toast says so.

PropTypeDefaultDescription
getKey (required)(item: T) => string–A stable key for each item. Rows are tracked by key, so an item removed and restored keeps its identity.
renderItem (required)(item: T, ctx: UndoRowContext) => ReactNode–Renders one row. ctx.remove(kind) removes it with an action kind, ctx.removed is true while it is folded away and ctx.restoredAt changes each time it is put back.
label (required)string–Accessible name of the list.
itemsreadonly T[]–Controlled items. Removed rows stay in this array, folded away, until the batch is committed; then onItemsChange receives the list without them.
defaultItemsreadonly T[]–Starting items when uncontrolled.
onItemsChange(items: T[]) => void–Called with the new list when a batch is committed, never while it can still be undone.
actionsRecord<string, UndoAction>archive and deleteThe kinds of removal. Each has a label ("Archive", used in failure copy), done ("archived", used in the toast) and an optional icon. The first key is the default kind for ctx.remove().
nounreadonly [string, string]["item", "items"]Singular and plural nouns for the toast: "Conversation archived", "3 conversations archived". Mixed batches read "2 archived, 1 deleted".
durationnumber6000Milliseconds the undo window stays open. It refills whenever another row joins the batch and pauses while the toast is hovered or focused or the tab is hidden.
onRemove(entries: UndoEntry<T>[]) => void–Called as soon as a row is removed, for optimistic server updates. Pair it with onUndo to reverse them.
onUndo(entries: UndoEntry<T>[]) => void | Promise<unknown>–Called on undo. Return a promise to show Restoring with a spinner; a rejection keeps the rows removed, says Couldn't undo, try again, and restarts the countdown.
onCommit(entries: UndoEntry<T>[]) => void | Promise<unknown>–Called when the undo window closes, with every entry in the batch and its original index. Return a promise; a rejection opens the rows again and shows Couldn't archive conversation.
undoShortcutbooleantrueCmd+Z on Apple devices and Ctrl+Z elsewhere undo the pending batch, unless focus is in a text field that has its own undo. The Undo button shows the shortcut on devices with a pointer.
pauseOnHoverbooleantrueHovering or focusing the toast pauses the countdown.
emptyReactNode–Shown, fading up, once every row is gone.
toastPlacement"container" | "viewport""container"container floats the toast over the bottom of the component, so reserve padding at the bottom of the list. viewport fixes it to the bottom of the window.
refRef<UndoToastHandle>–remove(key, kind), undo() and commit() for toolbars, bulk actions and tests.
classNamestring–Class on the root element.
styleCSSProperties–Inline styles on the root element.

UndoToastRow

A ready made row: leading media, a title and description, and metadata that gives way to the action buttons on hover and focus without reflowing. The row is focusable and runs single key shortcuts while focused.

PropTypeDefaultDescription
title (required)ReactNode–The first line, truncated with an ellipsis.
leadingReactNode–Media before the text, such as an avatar.
descriptionReactNode–The second line, truncated with an ellipsis.
metaReactNode–Right aligned metadata such as a time. It fades out while the actions are shown.
actionsReactNode–Row buttons, usually UndoToastRowAction. Shown on hover and focus, and always on touch screens.
shortcutsRecord<string, () => void>–Single key shortcuts on the focused row, such as { e: archive, Backspace: remove }.
labelstring–Accessible name of the row, such as the sender and subject.
unreadboolean–Sets the title in medium weight.
classNamestring–Class on the row.

UndoToastRowAction

A round icon button for a row action, named by its label.

PropTypeDefaultDescription
label (required)string–Accessible name and tooltip, such as Archive.
icon (required)ReactNode–A 16px lucide icon.
onClick (required)() => void–Usually ctx.remove(kind). The click does not bubble to the row.

Keyboard interactions

KeysAction
TabMoves through the rows, their actions and the Undo button.
E / Backspace / DeleteWith UndoToastRow shortcuts, archive or delete the focused row. Focus moves to the same control on the next row, or the previous one at the end of the list.
Cmd+Z / Ctrl+ZUndoes the pending batch from anywhere on the page, except inside a text field.
Enter / SpaceOn Undo, restores the batch.
EscapeWith focus in the toast, closes the undo window now and commits the batch.

Accessibility

Motion

Responsive behavior

Performance

Notes for AI

Also in messages

Guidance for AI tools

Undo toast: Archive or delete rows with a second chance: the gap closes, a countdown toast merges repeat actions, and Undo or ⌘Z puts every row back exactly where it was. Follow the declared prop types and do not invent props. Keep keyboard access, reduced motion support, and both light and dark themes intact when adapting it.

Full library index: https://ui.xzero.co.uk/llms.txt

Browse all components