Skip to main content
On this page

Destructive actions

Decide how much friction an irreversible action needs, and when to offer a way back instead.

Overview​

Destructive actions remove or overwrite data: deleting a record, cancelling a contract, issuing a refund, overriding a meter reading. This pattern covers how much friction to put in front of them, and when to offer a way back instead.

This pattern does not cover who is allowed to perform an action. See Permission restrictions.

The core decision​

How much friction an action deserves depends on how costly it is to reverse.

  • If cheap to reverse, let the user get on with the task and make reversing it easy to find.
  • If costly or impossible to reverse, add friction in proportion to the cost, and explain in plain language what the consequence is.

Most actions do not need that much friction. Keep it for consequences that are serious and cannot be put right.

Be sympathetic to how frequently your user performs the action. A confirmation seen many times a day will lose its impact, and protects less each time it appears. Where an action is routine, make it reversible rather than confirming it, and keep it on the page instead of behind a modal.

Reversible actions​

Where an action can be easily undone, do not interrupt the user. Let them perform it, then offer a way back if needed.

  • It's not always necessary to ask first. If the user can put a reversible action right themselves, a confirmation costs them time without preventing negative outcomes.
  • Offer undo immediately after the action, in a Notification. Notifications do not dismiss themselves, so the undo stays until the user acts on it or clears it. It does go when they navigate away, so it covers the mistake they have just made rather than one they find later.

If the action cannot be undone once that notification is gone, it is not really reversible, and it belongs in the section below.

Loading...
Edit live code
() => {
  // ignore - for live code example only.
  const [deleted, setDeleted] = useState(false);

  return deleted ? (
    <Notification
      id="delete-payment-method"
      variant="success"
      title="Payment method deleted"
      dismissable
      actions={[
        { id: 'undo-delete', label: 'Undo', onClick: () => setDeleted(false) },
      ]}
    />
  ) : (
    <DestructiveCTAButton onClick={() => setDeleted(true)}>
      Delete payment method
    </DestructiveCTAButton>
  );
}

Irreversible actions​

Where an action cannot be undone, put a friction modal in front of it: a Modal whose only job is to state the consequence and make the user stop and read it.

  • State the consequence as a fact, not a question. See Copy guidance.
  • Keep the emphasis on the confirming action, not on the control that opens it. Which variant to use for each is set out in Button choice below.
  • One primary action per modal.
  • Never open a modal from a modal. If a task inside a modal needs its own confirmation, that first task shouldn't have been in a modal.
  • Do not rely on colour alone to carry the warning. The title and the button text must make the consequence clear without it.
Loading...
Edit live code
() => {
  // ignore - for live code example only.
  const [isOpen, setIsOpen] = useState(false);

  return (
    <>
      <DestructiveSecondaryCTAButton onClick={() => setIsOpen(true)}>
        Cancel contract
      </DestructiveSecondaryCTAButton>
      <Modal
        title="Cancel contract?"
        isOpen={isOpen}
        onClose={() => setIsOpen(false)}
        autoFocus
      >
        <Stack spaceBetween={4}>
          <P>This cannot be undone. The customer will need a new contract before supply can resume.</P>
          <Cluster spaceBetween={2}>
            <ClusterItem>
              <DestructiveCTAButton onClick={() => setIsOpen(false)}>
                Cancel contract
              </DestructiveCTAButton>
            </ClusterItem>
            <ClusterItem>
              <SecondaryCTAButton onClick={() => setIsOpen(false)}>
                Keep contract
              </SecondaryCTAButton>
            </ClusterItem>
          </Cluster>
        </Stack>
      </Modal>
    </>
  );
}

Repeated and bulk actions​

A confirmation that appears every time will lose impact. For actions a user performs routinely, prefer making the action reversible over confirming it.

Where a bulk action is irreversible, state the scale in the confirmation rather than leaving it to be inferred: 'Delete 12 accounts?' tells the user something 'Delete selected accounts?' does not.

On preventing a double submission: Nebula has no disabled states, with one exception that applies here. CTA Button has a loading prop that replaces the button content with a loading indicator and disables the button while the action is in flight. That is the sanctioned guard against a double click. It is not a substitute for the operation being idempotent on the server, which is what actually protects against a repeated request.

Button choice​

Match the variant to how prominent the action should be.

Use the filled destructive button for the action itself, where it's the main action on the page or in the modal.

Loading...
Edit live code
<DestructiveCTAButton>Delete account</DestructiveCTAButton>

Use the outlined destructive link when navigating to a destructive action or a prompt, rather than performing it. There is no filled destructive link, so the filled button only ever appears on the destination.

Loading...
Edit live code
<DestructiveSecondaryCTALink href="">Cancel my green plan</DestructiveSecondaryCTALink>

Use the tertiary destructive button where a contained button doesn't work in the layout.

Loading...
Edit live code
<DestructiveTertiaryCTAButton>Remove payment method</DestructiveTertiaryCTAButton>

Use a plain tertiary button, with no red at all, where the action needs real de-emphasis. This is most relevant for tables: one destructive action per row might mean a column of red buttons, which will dilute the impact of red as a warning colour. In these cases you can de-emphasise the trigger and let the confirmation carry the weight instead.

Loading...
Edit live code
<TertiaryCTAButton>Remove</TertiaryCTAButton>

See CTA Button and CTA Link for the full set.

Accessibility​

  • Focus is trapped inside the modal while it is open, and returns to the trigger on close. Modal does both by default, and Escape closes it.
  • Pass autoFocus to move focus into the modal when it opens. This one is opt-in. Without it focus stays where it was, and a keyboard user has to tab into the dialog to reach the buttons.
  • Land focus so the consequence is announced before a button is reachable. Focusing the first focusable element will land the user directly on a button they might activate by reflex. Putting initial focus on the modal itself means the title and the consequence are read first. This is our own position rather than established practice, so it is worth testing.
  • Never communicate destructiveness through colour alone. Not every user can see the colour or knows what it signifies.
  • Announce the outcome, not just the prompt. A screen reader user needs to know the action completed, and that undo is available, without seeing a notification appear.

Copy guidance​

Name the action in the title​

Do

Delete customer?

Don't

Are you sure you want to delete customer?

State the consequences​

These should be phrased as a fact, not as a question.

Do

This cannot be undone.

Don't

Are you sure? You will not be able to reverse this.

Label the button with a verb​

Use the same verb as the title, such as Add, Delete or Save.

Do

Delete

Don't

OK

Yes

Done

Confirm

Don't joke or soften​

Someone cancelling a vulnerable customer's contract is not in a light moment.

See Error messages for system message wording.

References​