Skip to content

frontend: replace window.confirm() with accessible <ConfirmDialog> primitive #56

Description

@countercheck

Source: codebase audit, 2026-06-11 (audit finding F2, Tier 1)
Severity: Medium
Category: Accessibility + UX consistency

Problem

Six destructive operator flows use native window.confirm(). The browser dialog:

  • has no focus trap (the page underneath remains tab-reachable)
  • has no visual consistency with the rest of the admin shell (no Tailwind tokens, no Alert/Card palette)
  • has no aria-modal / role="dialog"
  • doesn't return focus to the trigger button on close

Sites:

Suggested approach

Add frontend/src/ui/ConfirmDialog.tsx:

```tsx
type Props = {
open: boolean;
title: string;
body: ReactNode;
confirmLabel: string;
cancelLabel?: string;
tone?: 'danger' | 'default';
onConfirm: () => void;
onCancel: () => void;
};
```

Required behavior:

  • role="dialog", aria-modal="true", aria-labelledby on the title
  • Focus moves to the confirm button on open; returns to the trigger on close
  • Escape → cancel; Enter → confirm
  • Click outside the dialog body → cancel
  • Body scroll lock while open
  • Matches the existing Alert/Card Tailwind palette (use danger-bg / danger tokens for tone='danger')

Colocated test: open/close, Escape cancels, Enter confirms, Tab cycles within dialog, focus returns on close.

Test migration

Each rewired view's existing test that spies on window.confirm swaps to asserting ConfirmDialog renders and clicking the confirm button completes the action.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions