Skip to main content
iOS / SwiftUI · WCAG 2.2 AA

Dialog (Confirmation)

A native SwiftUI .confirmationDialog action sheet that takes VoiceOver focus on presentation, with each action returning focus to the trigger because native confirmation dialogs do not restore it automatically.

Selection

The criteria an agent checks before retrieving this component.

Use when

  • Use when a control offers a short set of actions to confirm or choose from, presented as an action sheet anchored to that control (e.g., confirming a destructive delete, picking one of a few actions on an item). Uses the native SwiftUI .confirmationDialog(_:isPresented:titleVisibility:actions:message:) modifier.
  • Use when the choice is a small set of clearly labeled actions (typically two to four) plus a cancel.

Try a different component when

  • Do not use when a brief, blocking message needs the user to acknowledge it or make a small decision before continuing (use dialog.alert).
  • Do not use when the content is a larger form or multi-step task (use dialog.modal).
  • Do not use when the items are commands pulled down from a control rather than a one-time confirmation or choice (use menu.basic).
  • Do not use when the user is choosing a single persistent value rather than committing an action (use select.menu).

Must Haves

Non-negotiable structure. Every generated instance must satisfy these rules.

  • Use the native .confirmationDialog(_:isPresented:titleVisibility:actions:message:) modifier so the action sheet is a real overlay that takes VoiceOver focus on presentation and blocks interaction with the rest of the screen until it is dismissed.
  • Provide the primary question or statement as the dialog title, and put any supporting detail in the message: closure.
  • Set titleVisibility: .visible so the title is shown and spoken, unless the triggering context already makes the choice clear (see Customizable).
  • Return VoiceOver focus to the trigger on dismissal: bind the trigger with @AccessibilityFocusState and set it true inside every action's closure, because native confirmation dialogs do not restore focus automatically, which is an Apple platform defect (WCAG 2.4.3). See global.focus-management.
  • Give each action a specific label and the correct role: .cancel for the dismissive action and .destructive for a destructive one, so VoiceOver and the system present them correctly.
  • Keep the action set short and the labels self-explanatory out of context (e.g., "Discard Draft", "Keep Editing"), not "OK"/"Yes"/"No" where the outcome is ambiguous.
  • Meets the touch target size baseline in global_rules.md (global.touch-target-size).
  • Meets the system focus indicator baseline in global_rules.md (global.focus-visible).

Donts

Avoid these accessibility and UX barriers.

  • Do not build a custom view as a faux action sheet (a conditional VStack overlay); it does not receive VoiceOver focus on display, does not block the background, and does not restore focus on close. Use the native .confirmationDialog(), or dialog.modal for a richer custom modal.
  • Do not omit focus return; without @AccessibilityFocusState set in each action, VoiceOver focus is lost when the dialog closes, which is a gap in the native control.
  • Do not rely on color alone to signal a destructive action; use the .destructive role and a clear label, not only red text.
  • Do not put forms, many controls, or lengthy content in a confirmation dialog; use dialog.modal for that.

Customizable

Alternatives and options that give the AI agent some room to move.

  • The dialog may present a single confirming action plus cancel, or several actions plus cancel, as long as every action returns focus to the trigger.
  • The confirming action may carry the .destructive role, or the default role when the action is not destructive.
  • The title may be hidden with titleVisibility: .hidden when the triggering context already makes the choice clear, as long as the message or action labels still convey what is being confirmed.

Golden Pattern

The tested reference implementation. Agents start from this shape and adapt to the developer’s codebase and context.

SWIFT
import SwiftUI

struct DialogConfirmationDemo: View {
@State private var showingDialog = false
@AccessibilityFocusState private var triggerFocused: Bool

var body: some View {
Button("Discard Draft", role: .destructive) {
showingDialog = true
}
.accessibilityFocused($triggerFocused)
.confirmationDialog(
"Discard this draft?",
isPresented: $showingDialog,
titleVisibility: .visible
) {
// Each action returns VoiceOver focus to the trigger, since native
// confirmation dialogs do not restore it automatically.
Button("Discard Draft", role: .destructive) {
print("Draft discarded")
triggerFocused = true
}
Button("Keep Editing", role: .cancel) {
triggerFocused = true
}
} message: {
Text("Your unsent changes will be lost.")
}
}
}

Acceptance Checks

The component’s test spec — an optional body of checks for verification.

Traits & semantics

  • The confirmation dialog is an overlay presented over the screen; while it is open, only its content and actions are reachable.

VoiceOver

  • When the dialog opens, VoiceOver focus moves into it and the title and message are announced.
  • Each action button speaks its specific label (e.g., "Discard Draft", "Keep Editing").
  • When any action dismisses the dialog, VoiceOver focus returns to the trigger button.

Switch Control & Full Keyboard Access

  • The dialog's actions are reachable and activatable via Switch Control and a hardware keyboard, and dismissing returns focus to the trigger.

Dynamic Type

  • The dialog's title, message, and action labels scale with Dynamic Type and stay fully visible.

Visual

  • Action labels are specific and do not depend on color alone. Native confirmation dialog button text contrast can fall short (an Apple platform defect); verify against your target appearances.