Accessibility pattern · Buttons and actions

Destructive action with undo

Delete acts at once and moves focus to the next item's Delete button, so keyboard users keep their place in the list. A toast offers Undo and waits while it is pointed at or focused, and Ctrl+Z still undoes after it has gone, so undoing is never a race.

WCAG criteria
6
Keyboard rules
5
Checked with
axe, keyboard and the inspector

Live demo

Try it

Use it with a mouse, a keyboard or a screen reader. The inspector beside it shows what the browser tells assistive technology as you go: focus, state changes and announcements.

Saved for later

Deleted one by mistake? Undo it from the message, or press Ctrl+Z.

  • Handloom cotton saree ₹2,450
  • Brass diya set ₹899
  • Terracotta planter ₹640
  • Block-print cushion cover ₹520

The demo works without JavaScript only as far as its HTML does; the inspector needs JavaScript.

Keyboard

Keys it answers to

Every action works without a pointer.

KeyWhat it does
Enter or SpaceOn Delete, removes the item and moves focus to the next item's Delete button, or the previous one, or the list heading when nothing is left.
CtrlZ or ⌘ZRestores the last deleted item while focus is in the demo, even after the toast has closed.
TabFrom the list, reaches the toast's Undo button; the toast stops counting down while it has focus.
EnterOn Undo, puts the item back where it was and moves focus to its Delete button.
EscapeIn the toast, dismisses it and returns focus to the list.

Screen readers

What it announces

Written from the roles, names and states in the markup.

WhenExpected announcement
Focus reaches a Delete buttonDelete Brass diya set, button
Delete is pressedDelete Terracotta planter, button. Brass diya set removed. Press Ctrl+Z to undo.
Undo is pressedDelete Brass diya set, button. Brass diya set restored.
The last item is deletedSaved for later, heading level 3. Block-print cushion cover removed. Press Ctrl+Z to undo.

These are expected announcements, not recordings. Wording and order differ between screen readers and browsers.

Code

Copy the code

The exact files this demo runs on. The styles are served with a prefix that keeps this site's own styles out of the demo; what you copy is the original.

The markup

<div class="ap-confirm-action" data-ap-confirm-action>
  <div class="ap-confirm-action__card">
    <div class="ap-confirm-action__top">
      <h3 class="ap-confirm-action__label" id="confirm-action-list" tabindex="-1">Saved for later</h3>
      <p class="ap-confirm-action__hint">Deleted one by mistake? Undo it from the message, or press <kbd>Ctrl</kbd>+<kbd>Z</kbd>.</p>
    </div>
    <ul class="ap-confirm-action__list" aria-labelledby="confirm-action-list">
      <li class="ap-confirm-action__row">
        <span class="ap-confirm-action__art ap-confirm-action__art--cloth" aria-hidden="true"><svg viewBox="0 0 24 24" focusable="false"><path d="M5 4h14l-2 16H7Z"/><path d="M9 4c0 4 6 4 6 0M8 12h8"/></svg></span>
        <span class="ap-confirm-action__text">
          <span class="ap-confirm-action__name" id="confirm-action-name-1">Handloom cotton saree</span>
          <span class="ap-confirm-action__price">₹2,450</span>
        </span>
        <button type="button" class="ap-confirm-action__delete" id="confirm-action-del-1" aria-labelledby="confirm-action-del-1 confirm-action-name-1" data-ap-delete>
          <svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M4 7h16"/><path d="M9 7V4h6v3"/><path d="M6.5 7l1 13h9l1-13"/></svg>
          Delete
        </button>
      </li>
      <li class="ap-confirm-action__row">
        <span class="ap-confirm-action__art ap-confirm-action__art--lamp" aria-hidden="true"><svg viewBox="0 0 24 24" focusable="false"><path d="M4 13h16c0 4-3.6 6-8 6s-8-2-8-6Z"/><path d="M12 13c-2-2-1-5 0-8 1 3 2 6 0 8Z"/></svg></span>
        <span class="ap-confirm-action__text">
          <span class="ap-confirm-action__name" id="confirm-action-name-2">Brass diya set</span>
          <span class="ap-confirm-action__price">₹899</span>
        </span>
        <button type="button" class="ap-confirm-action__delete" id="confirm-action-del-2" aria-labelledby="confirm-action-del-2 confirm-action-name-2" data-ap-delete>
          <svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M4 7h16"/><path d="M9 7V4h6v3"/><path d="M6.5 7l1 13h9l1-13"/></svg>
          Delete
        </button>
      </li>
      <li class="ap-confirm-action__row">
        <span class="ap-confirm-action__art ap-confirm-action__art--pot" aria-hidden="true"><svg viewBox="0 0 24 24" focusable="false"><path d="M5 9h14l-2 11H7Z"/><path d="M4 9h16M12 9V6c0-2 2-3 4-3M12 6c0-2-2-3-4-3"/></svg></span>
        <span class="ap-confirm-action__text">
          <span class="ap-confirm-action__name" id="confirm-action-name-3">Terracotta planter</span>
          <span class="ap-confirm-action__price">₹640</span>
        </span>
        <button type="button" class="ap-confirm-action__delete" id="confirm-action-del-3" aria-labelledby="confirm-action-del-3 confirm-action-name-3" data-ap-delete>
          <svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M4 7h16"/><path d="M9 7V4h6v3"/><path d="M6.5 7l1 13h9l1-13"/></svg>
          Delete
        </button>
      </li>
      <li class="ap-confirm-action__row">
        <span class="ap-confirm-action__art ap-confirm-action__art--cushion" aria-hidden="true"><svg viewBox="0 0 24 24" focusable="false"><rect x="4" y="5" width="16" height="14" rx="3"/><circle cx="12" cy="12" r="2.5"/><path d="M8 9h.01M16 9h.01M8 15h.01M16 15h.01"/></svg></span>
        <span class="ap-confirm-action__text">
          <span class="ap-confirm-action__name" id="confirm-action-name-4">Block-print cushion cover</span>
          <span class="ap-confirm-action__price">₹520</span>
        </span>
        <button type="button" class="ap-confirm-action__delete" id="confirm-action-del-4" aria-labelledby="confirm-action-del-4 confirm-action-name-4" data-ap-delete>
          <svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M4 7h16"/><path d="M9 7V4h6v3"/><path d="M6.5 7l1 13h9l1-13"/></svg>
          Delete
        </button>
      </li>
    </ul>
    <p class="ap-confirm-action__empty" data-ap-nothing hidden>Nothing saved. Items you save for later appear here.</p>
  </div>

  <div class="ap-confirm-action__shelf">
    <div class="ap-confirm-action__toast" data-ap-toast hidden>
      <svg class="ap-confirm-action__toast-icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M4 7h16"/><path d="M9 7V4h6v3"/><path d="M6.5 7l1 13h9l1-13"/></svg>
      <p class="ap-confirm-action__msg"><span class="ap-confirm-action__what" data-ap-what></span> <span>removed</span></p>
      <button type="button" class="ap-confirm-action__undo" data-ap-undo>Undo</button>
      <button type="button" class="ap-confirm-action__x" data-ap-dismiss aria-label="Dismiss">
        <svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M6 6l12 12M18 6 6 18"/></svg>
      </button>
      <span class="ap-confirm-action__timer" aria-hidden="true"></span>
    </div>
  </div>
  <p class="ap-confirm-action__say" role="status"></p>
</div>

WCAG 2.2

What it meets

The success criteria this pattern takes care of, and how.

  • 2.1.1 Keyboard Level A

    Delete, Undo, Dismiss and Ctrl+Z all work from the keyboard.

  • 2.2.1 Timing Adjustable Level A

    The toast pauses while it is hovered or focused, an option keeps it until dismissed, and Ctrl+Z still undoes after it closes, so undo has no hard time limit.

  • 2.4.3 Focus Order Level A

    After a delete, focus moves to the next Delete button, the previous one or the heading, never to the top of the page.

  • 3.3.4 Error Prevention (Legal, Financial, Data) Level AA

    Deleting is reversible: Undo puts the item back in place, which this criterion accepts instead of asking for confirmation.

  • 4.1.2 Name, Role, Value Level A

    Each Delete button is named with its item, such as Delete Brass diya set, so a list of buttons still makes sense out of context.

  • 4.1.3 Status Messages Level AA

    The removal and the restore are announced from a polite status, without pulling focus into the toast.

Usage

When to use it

Use it

  • Removing things people can easily get back: saved items, drafts, list entries, notifications.
  • Lists where people delete several things in a row, and a dialog each time would slow them down.

Use something else

  • Actions that cannot be undone, like closing an account or sending money: ask first with an alert dialog.
  • Deletes that reach a server at once with no way back: make them reversible first, or confirm.

Common failures

How it usually goes wrong

  • Focus lost with the item

    When the focused button is removed, focus falls back to the top of the page. Moving it to the next item keeps people in the list.

  • An Undo that runs out

    A toast that closes after a few seconds can take the only undo with it before a screen reader user reaches it. This one waits on hover and focus, and Ctrl+Z works after it.

  • Pulling focus into the toast

    Moving focus to the toast after every delete drags people out of the list they are working through. The toast is announced; Tab reaches it when wanted.

  • Are you sure? every time

    Confirmation dialogs on routine deletes train people to press Yes without reading. A real undo protects them better and costs nothing when unused.

  • A list of buttons all called Delete

    Out of context, Delete, Delete, Delete says nothing about what goes. aria-labelledby adds the item's name to each button.

  • Deleting on the server straight away

    If the record is gone before the toast closes, Undo has to re-create it. Commit the delete when the undo window ends, or keep a bin.

Notes

Building it

  • Each Delete button is named by aria-labelledby pointing at itself and at the item's name, so the visible word comes first and nothing is written twice.
  • Deleted items wait in a stack with their positions; Undo and Ctrl+Z put back the most recent one where it was.
  • The countdown pauses, rather than restarts, on hover and focus, and also while the browser tab is in the background.
  • The thin bar along the bottom of the toast shows the time left; it is decoration, hidden from assistive technology and switched off for reduced motion.

Sources: Understanding WCAG 2.2: Timing Adjustable · Understanding WCAG 2.2: Error Prevention (Legal, Financial, Data)

Checked with axe in light and dark themes, at desktop and phone widths, and by keyboard. Not yet tested with every screen reader and browser pair. Report a correction