Skip to content
ExamsLibraryFor agentsConnect your agent

Instant actions, undo and toasts

intermediate·9 min·Markdown for agents

What this lesson teaches

Make likely-success actions feel instant, choose between undo and a confirmation for destructive ones, and use toasts (and Sonner) without the usual bugs.

  1. 1When to be optimistic
  2. 2Undo or confirm?
  3. 3A confirmation you can answer without reading the body
  4. 4The undo toast is not a normal toast
  5. 5Sonner in practice

Both hearts get tapped once as they scroll in. Same server, same 700ms delay; tap them again yourself.

Pessimistic: wait for the server
Maya ChenShipped the new checkout to 10% of traffic. Conversion is up 3.1% so far.
Optimistic: update now, reconcile later
Maya ChenShipped the new checkout to 10% of traffic. Conversion is up 3.1% so far.
Left waits for the server. Right updates now and only rolls back if the server says no.

The right one feels instant because it is instant, as far as you can tell. That is an : change the screen immediately, send the request in the background, and undo the change only if it fails. Tick "Make the server fail" to watch the rollback.

It is the single biggest speed win available without touching the servers.

When to be optimistic

const previous = liked;
setLiked(!liked);                // update now
try {
  await api.toggleLike(post.id); // confirm in the background
} catch {
  setLiked(previous);            // roll back
  toast.error("Couldn't update the like. Try again.");
}
Be optimisticDon't
Toggles: like, bookmark, follow, mutePayments and bookings: the confirmation is the design
Marking a to-do completeAnything only the server can validate, like "is this username free?"
Renaming with client-side checksSign-up and login, where a failure is information
Reordering a listDestructive actions without undo

If the server creates an id, give the new item a temporary client id and swap it when the response lands. And test the rollback path on purpose: a rollback that yanks the page somewhere else is worse than a 200ms spinner.

Undo or confirm?

Now the scary actions. Archive a project below, then try deleting the whole workspace.

acme-web · Projects
Atlas rebrand
Q3 report
Checkout redesign
Onboarding emails
Danger zone, kept apart from everything else

Archive acts at once and offers Undo for 6 seconds (hover or focus the toast to pause it). Delete workspace can't be undone, so it confirms: the title names the thing, the body counts the loss, focus starts on Cancel.

Archive acts at once with undo. Delete workspace can't be undone, so it asks you to type its name.

A confirmation on every delete trains people to click through it, so the one that matters gets clicked through too. Decide first:

The actionPattern
Reversible and frequent: archive, move, delete to trashDo it now; offer Undo in the result
Irreversible, one itemConfirm
Irreversible, many itemsConfirm, with the count
Affects other peopleConfirm, and say who: "The 8 members lose access"
An account, workspace or repositoryType its name to confirm; the button stays disabled until it matches

Undo is cheaper for the user and more honest. It is also the optimistic pattern: remove the row now, and let the real delete run during the undo window.

A confirmation you can answer without reading the body

✕ Before

Are you sure?
This action cannot be undone.
OK Cancel

✓ After

Delete workspace "acme-web"?
This permanently deletes 4 projects and their 212 files.
Cancel Delete workspace

  • Title names the action and the object. Never "Are you sure?".
  • Body says what is lost, with counts.
  • Buttons are verb plus object, never Yes and No.
  • Focus starts on Cancel, so a reflexive Enter does no harm.
  • Only destruction is red. If the brand itself is red, give destruction a different treatment (outlined, with an icon).
Delete sitting in a menu right under Rename, with no gap, no color and no undo, is the most common unguarded delete there is.

The undo toast is not a normal toast

A is a small message that slides in at the edge of the screen and leaves on its own. The undo toast carries an action, so it plays by stricter rules: at least 5 seconds, paused while hovered or focused, and announced politely without stealing focus. An undo link that vanishes on a timer is data loss on a schedule.

Try the stack: fire a few, then hover or tap it.

Fire a few. Hover, focus or tap the stack to spread it out; every timer pauses while you do.

Changes saved
Archived “Q3 report”.
Changes not saved. Check your connection and try again.
Newest in front. The stack spreads and every timer pauses while you look.

A plain "Changes saved" can leave after 4 seconds. An error that needs action stays until dismissed. Reading time scales with the words (about 200 to 250 words a minute), never a flat 2 seconds.

Sonner in practice

Sonner, by Emil Kowalski, is the toast library most React apps reach for. It needs exactly two pieces: one Toaster component mounted once near the root (the root layout in Next.js), and toast() called from client code.

NeedCall
A message with a second linetoast("Saved", { description })
A typed icontoast.success, .error, .info, .warning
Tied to a promisetoast.promise(p, { loading, success, error })
Update a toast laterconst id = toast.loading("…"), then toast.success("Done", { id })
A buttonaction: { label: "Undo", onClick }
Your own designtoast.custom((t) => …)

The bugs are almost always one of these:

SymptomCause
Nothing appearsNo Toaster mounted, or toast() ran on the server
Appears twiceTwo Toasters (layout and page), or a React effect ran twice; fire from the click handler or pass a stable id
Hidden behind a modalAn ancestor created a stacking context; mount the Toaster at the document root
Stuck on loadingThe promise never settles
"Dark mode is broken"theme defaults to "light"; pass theme="system". Colors stay gray until richColors is set
Optimistic with undo· for your agent
Make archiving instant: remove the row immediately, then call the API. Show a Sonner toast "Archived 'NAME'." with an Undo action that restores the row at its old position and cancels the request.
- The toast stays at least 6s, pauses on hover and focus, and does not take focus.
- If the API fails, restore the row and toast.error("Couldn't archive. Restored.").
- Move focus to the next row's action (or the previous one) when the focused row disappears.
- One Toaster, with theme="system" and richColors, in the root layout only.
For deleting the workspace, use our dialog: title "Delete workspace 'NAME'?", counts in the body, Cancel focused, "Delete workspace" disabled until the name is typed.

Quick check

Which action should get an undo toast instead of a confirmation dialog?

What this lesson builds on and leads to, across every course.

  • Routing the job
  • Every list has four states
  • What is a skill?