MobTranslate styleguide

Confirmation and Undo

Match consequence to interruption, commit only the actual result, and keep recovery where people can still find it.

Choose the smallest sufficient interruption

ConsequenceTreatmentEvidence
Harmless reversible preferenceImmediate action with visible stateActual selected/applied value.
Reversible local removalClear action and persistent Undo/RestoreLocal state changes and recovery restores it.
Consequential choiceNamed AlertDialog with safe initial focusExplicit consent to the described operation, not general authority.
Complex editDialog/page with validation and retained draftActual saved result; failure preserves input.
Irreversible operationExplain exact consequence and absence of recoveryAuthorization and durable outcome outside the UI demonstration.

Model the operation separately from the surface

  • Opening a confirmation is not executing it. Cancel/Escape keep the object unchanged.
  • Confirmation can begin a pending operation. Prevent duplicates, offer cancellation if it is real, and retain the request identity for reconciliation.
  • A completed request commits its actual returned/persisted result. A failed or ambiguous request needs retry/reconciliation, not an invented success notification.
  • Undo is an operation with its own pending/failure states. Do not offer it after the recovery window expires or when permissions no longer permit it.
  • A dismissed message is not an undo. A modal close is not an abort. A green state is not evidence of language review or consent.

Working local consequence and recovery

Local example draft is present.

tsx
'use client';
import { useRef, useState } from 'react';
import { AlertDialog, AlertDialogBackdrop, AlertDialogClose, AlertDialogDescription, AlertDialogPopup, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger, Button } from '@mobtranslate/ui';

export function ConfirmationContainmentExample() {
  const [open, setOpen] = useState(false);
  const [exists, setExists] = useState(true);
  const [result, setResult] = useState('Local example draft is present.');
  const cancel = useRef<HTMLButtonElement>(null);
  const restore = useRef<HTMLElement>(null);
  return <div data-confirmation-lab className="space-y-4">
    <AlertDialog open={open} onOpenChange={setOpen}>
      <AlertDialogTrigger disabled={!exists} render={<Button type="button" variant="destructive" />}>Remove local example</AlertDialogTrigger>
      <AlertDialogPortal><AlertDialogBackdrop /><AlertDialogPopup initialFocus={cancel} finalFocus={() => exists ? true : restore.current} className={state => state.open ? 'local-confirmation-open' : ''}>
        <AlertDialogTitle>Remove this local example?</AlertDialogTitle><AlertDialogDescription>This changes only the example on this page. A separate restore action remains available. No source, recording or database row will be deleted.</AlertDialogDescription>
        <div className="flex flex-wrap gap-3 mt-6"><AlertDialogClose ref={cancel} render={<Button type="button" variant="outline" />}>Keep local example</AlertDialogClose><Button type="button" variant="destructive" onClick={() => { setExists(false); setResult('Local example removed. Use Restore local example to recover.'); setOpen(false); }}>Confirm local removal</Button></div>
      </AlertDialogPopup></AlertDialogPortal>
    </AlertDialog>
    <Button ref={restore} type="button" variant="outline" aria-disabled={exists || undefined} onClick={() => { if (!exists) { setExists(true); setResult('Local example restored.'); } }}>Restore local example</Button>
    <p role="status">{result}</p>
  </div>;
}

Focus through each outcome

Focus the safe action on opening a consequential choice. On cancellation, return to the trigger. On a result that disables the trigger, return to an enabled restore or the next logical item. Keep recovery reachable even when a toast expires or is displaced by its display limit.

  • Keep action labels specific and announce a concise actual result.
  • Long explanations retain semantic paragraphs; a short connected description names the immediate consequence.
  • Test both pointer and keyboard activation. Do not rely on automatic focus restoration when your result removed the invoking control.

Authority and persistence boundary

These examples operate on local English-only sample state. They do not prove deletion, ingestion, permission, review, publication or cultural authority. In a real application, retain operation receipts and source evidence and reconcile the durable result before describing it as complete.

Failure cases

  • An outside click executing the consequence.
  • Safe and destructive actions with indistinguishable names or accidental form submission.
  • A restore action available only in an expired toast.
  • A pending operation hidden by a closed surface without any result reconciliation.
  • Focus sent to a disabled trigger after the object was removed.

Verification and release scope

  • Verify the actual title, description relationships, focus destination, keyboard operation and task result in the consuming application.
  • Exercise narrow and short viewports, both themes, long labels, text spacing, reduced motion and forced colors. Reach the last action by scrolling and keyboard.
  • For overlays, check background interaction, scroll locking and each permitted dismissal path. A role or aria-modal attribute alone does not prove modal behavior.
  • Use a named screen reader and browser for spoken output. Local examples and automated DOM checks do not prove announcements or a persisted backend result.
  • Retain source/review/permission evidence. A container or confirmation cannot grant authority or establish that a request succeeded.