Skip to content

VaulType Accessibility Audit — WCAG 2.1 Level AA

This document is the official WCAG 2.1 Level AA compliance audit checklist for every user-facing surface in VaulType. It is intended to be executed manually before each public release and whenever a UI component is added or substantially changed.

Standard targeted: WCAG 2.1 Level AA (W3C Recommendation 5 June 2018) Platform: macOS 14.0+ (Sonoma)

Primary testing tools:

ToolPurpose
VoiceOver (macOS built-in)Screen-reader compliance, focus order, announcements
Accessibility Inspector (Xcode)Accessibility tree inspection, label/hint auditing
Color Contrast Analyser (macOS)Foreground/background contrast ratio verification
Xcode Accessibility Audit APIAutomated element auditing in XCUITest
VaulTypeTests/AccessibilityAuditTests.swiftUnit-level announcement and preference tests

Scope — UI surfaces covered:

  1. Menu Bar Popover (MenuBarView)
  2. Settings Window — 10 tabs (SettingsView)
  3. Overlay Window (OverlayContentView inside OverlayWindow)
  4. Onboarding Wizard (OnboardingView)
  5. History Window (HistoryView)

Out of scope: System menus, OS-level dialogs (permission prompts), Sparkle update sheets (third-party), and any future web or iOS surfaces.


macOS Accessibility Settings to Enable Before Testing

Section titled “macOS Accessibility Settings to Enable Before Testing”

Navigate to System Settings > Accessibility and configure the following for full coverage:

SettingLocationEnable For
VoiceOverVision > VoiceOverAll VoiceOver tests
Increase ContrastDisplay > Increase ContrastSection 7 — High Contrast
Reduce MotionAccessibility > Motion > Reduce MotionSection 7 — Reduce Motion
Reduce TransparencyAccessibility > Display > Reduce TransparencyOverlay transparency test
Keyboard NavigationKeyboard > Keyboard NavigationSection 5 — Keyboard
Full Keyboard AccessKeyboard > Keyboard Shortcuts > All ControlsSection 5 — Keyboard
  1. Launch VoiceOver: press Cmd+F5 or navigate via System Settings.
  2. Set verbosity to maximum: VoiceOver Utility > Verbosity > All.
  3. Enable “Speak items under mouse cursor” to aid cursor-based auditing.
  4. Keep Accessibility Inspector open alongside VoiceOver for tree inspection.
  5. Disable “Cursor tracking follows VoiceOver cursor” during keyboard-only tests.
  1. Open Xcode > Open Developer Tool > Accessibility Inspector.
  2. Choose the VaulType process from the target picker.
  3. Enable Audit tab to run automated checks per-window.
  4. Enable Inspection mode (crosshair icon) to click-inspect any element.
Terminal window
xcodebuild test \
-scheme VaulType \
-destination 'platform=macOS' \
-only-testing:VaulTypeTests/AccessibilityAuditTests

All tests in AccessibilityAuditTests must pass before a release is considered audited.


1.1 — Text Alternatives for Non-Text Content (WCAG 1.1.1, Level A)

Section titled “1.1 — Text Alternatives for Non-Text Content (WCAG 1.1.1, Level A)”

Every non-text element (icon, image, graphic, progress indicator) must have a text alternative conveying the same information, or be marked decorative so screen readers skip it.

General rule applied in VaulType codebase:

  • Decorative SF Symbols use .accessibilityHidden(true).
  • Combined elements use .accessibilityElement(children: .combine) + .accessibilityLabel(...).
  • Interactive controls use .accessibilityLabel(...) + .accessibilityHint(...).
ElementExpected label / treatmentPassFailN/ANotes
Red circle (recording indicator).accessibilityHidden(true) — parent HStack combines to “Status: Recording”
ProgressView (processing indicator).accessibilityHidden(true) — parent HStack combines to “Status: Processing transcription”
Mode icon (appState.activeMode.iconName).accessibilityHidden(true) — parent combines to “Mode: , Language:
Error triangle icon.accessibilityHidden(true) — parent combines to “Error:
History button clock iconDecorative within button; button label “History” sufficient
Settings gear iconDecorative within button; button label “Settings” sufficient
Quit power iconDecorative within button; button label “Quit VaulType” sufficient
ElementExpected label / treatmentPassFailN/ANotes
Mode icon in headerHidden or combined with parent label
ProgressView (processing header)Combined with “Processing…” text
ProgressView (transcribing body)Combined with “Transcribing…” text
Edit pencil icon (Edit button)Label("Edit", systemImage:) — SF label provides text automatically
ElementExpected label / treatmentPassFailN/ANotes
Progress dot circles (5 total)Parent combines to “Setup progress: step N of 5”
Individual step dotsEach has .accessibilityLabel("Step N completed / current / N")
Decorative illustrations (if any)Must be .accessibilityHidden(true)
Permission shield/mic iconsMust have labels if used as standalone graphics
TabSF SymbolExpected treatmentPassFailN/A
Generalgear.circleTab label “General” provided by Label
Audiowaveform.circleTab label “Audio”
ProcessingsparklesTab label “Processing”
Modelsarrow.down.circleTab label “Models”
App Profilesapps.iphoneTab label “App Profiles”
Vocabularytextformat.abcTab label “Vocabulary”
LanguageglobeTab label “Language”
Historyclock.arrow.circlepathTab label “History”
CommandscommandTab label “Commands”
Pluginspuzzlepiece.extensionTab label “Plugins”

Tab Label views automatically expose their text string as the accessibility label on macOS TabView. Verify via Accessibility Inspector that the tab bar items show the string label, not the SF Symbol name.


1.3 — Adaptable — Content Can Be Presented in Different Ways (WCAG 1.3.1–1.3.3, Level A)

Section titled “1.3 — Adaptable — Content Can Be Presented in Different Ways (WCAG 1.3.1–1.3.3, Level A)”
CriterionChecklist ItemPassFailN/ANotes
1.3.1Settings tabs expose role “tab” in accessibility tree (Accessibility Inspector)
1.3.1List items in History View expose row role
1.3.1Form labels in Settings are programmatically associated with their controls
1.3.1Section headings in Settings tabs (if any) exposed as headings
1.3.1Group containers in Onboarding use .accessibilityElement(children: .contain) or .combine correctly
1.3.1Error messages are adjacent to the control that caused them (or combined into the control’s label)
CriterionChecklist ItemPassFailN/ANotes
1.3.2VoiceOver reading order in Menu Bar Popover: Status → Mode → Last Transcription → Error (if shown) → Buttons
1.3.2VoiceOver reading order in Overlay: Header (mode, language, status) → Content → Footer buttons
1.3.2VoiceOver reading order in Onboarding: Progress indicator → Step content → Navigation buttons
1.3.2VoiceOver reading order in History: Search field → Filters → Entry list → Detail/action panel
CriterionChecklist ItemPassFailN/ANotes
1.3.3No instructions refer to shape/color/position alone (e.g., “click the red button”)
1.3.3Recording state communicated by text label, not only the red circle color
1.3.3Processing state communicated by text label, not only the spinner animation

1.4 — Distinguishable (WCAG 1.4.1–1.4.4, Level AA includes 1.4.3, 1.4.4)

Section titled “1.4 — Distinguishable (WCAG 1.4.1–1.4.4, Level AA includes 1.4.3, 1.4.4)”
CriterionChecklist ItemPassFailN/ANotes
1.4.1Error state in Menu Bar Popover conveyed by text “Error:” label, not orange color alone
1.4.1Quit button text is red — must also be identifiable by position or label, not color alone
1.4.1Onboarding progress dots — completed state conveyed by VoiceOver label, not fill color alone
1.4.1Active/inactive processing mode in Settings — not distinguished by color alone
1.4.1Favorites in History — star icon present; not indicated by color alone

1.4.3 — Contrast (Minimum) — 4.5:1 for normal text, 3:1 for large text (Level AA)

Section titled “1.4.3 — Contrast (Minimum) — 4.5:1 for normal text, 3:1 for large text (Level AA)”

Measure with Color Contrast Analyser. Test in both light and dark appearance.

Menu Bar Popover:

Text ElementForegroundBackgroundRequired RatioMeasured RatioPassFail
”Recording…” headline.primarypopover bg4.5:1
”Processing…” headline.primarypopover bg4.5:1
”Ready” (secondary).secondarypopover bg4.5:1
Mode name (caption, secondary).secondarypopover bg4.5:1
Language badge textaccent on accent/15% bg4.5:1
”Last Transcription” label (caption).secondarypopover bg4.5:1
Last transcription body text.primarypopover bg4.5:1
Error text (orange, caption).orangepopover bg4.5:1
Button text (History, Settings, Quit).primary / .redpopover bg4.5:1

Overlay Window:

Text ElementForegroundBackgroundRequired RatioMeasured RatioPassFail
Header mode text (caption).secondarymaterial/solid bg4.5:1
”Processing…” (caption2).secondarymaterial/solid bg4.5:1
Transcription body text.primarymaterial/solid bg4.5:1
”Transcribing…” (secondary).secondarymaterial/solid bg4.5:1
”Waiting for transcription…” (tertiary).tertiarymaterial/solid bg4.5:1
Cancel button text.primarybutton bg4.5:1
Inject button textwhite on accent4.5:1
Dismiss button text.primarybutton bg4.5:1
Edit button text.primarybutton bg4.5:1

Note: The overlay uses .ultraThinMaterial by default. Test with Reduce Transparency disabled (material background) and enabled (solid NSColor.windowBackgroundColor background). Contrast must pass in both states.

Onboarding Wizard:

Text ElementRequired RatioPassFailNotes
Step headings3:1 (large text, bold)
Step body text4.5:1
Button labels (Next, Allow, Download)4.5:1
Progress dot labels (if visible)N/A — VoiceOver onlyN/A

Settings Window (all tabs):

Text CategoryRequired RatioPassFailNotes
Section headers3:1 (if large/bold)
Control labels4.5:1
Picker/toggle labels4.5:1
Helper/description text (secondary)4.5:1
Disabled control text3:1 minimum

History Window:

Text CategoryRequired RatioPassFailNotes
Search field placeholder text4.5:1
Entry timestamp (secondary)4.5:1
Entry body text4.5:1
App name label (secondary)4.5:1
Filter picker text4.5:1
CriterionChecklist ItemPassFailN/ANotes
1.4.4All text in VaulType respects macOS “Larger Text” setting (System Settings > Accessibility > Display > Larger Text)
1.4.4Layout does not break or clip at larger text sizes
1.4.4Overlay window height adjusts or scrolls when content grows with larger text
1.4.4Settings tabs remain readable; no horizontal clipping of labels

2.1 — Keyboard Accessible (WCAG 2.1.1–2.1.2, Level A)

Section titled “2.1 — Keyboard Accessible (WCAG 2.1.1–2.1.2, Level A)”

Every interactive element must be reachable and operable with keyboard alone.

SurfaceChecklist ItemPassFailN/ANotes
Menu BarOpen popover via keyboard (activate menu bar item with VoiceOver + Space)
Menu Bar PopoverTab through all interactive elements: History button, Settings link, Quit button
Menu Bar PopoverActivate History button with Space/Return
Menu Bar PopoverActivate Settings link with Space/Return
Menu Bar PopoverActivate Quit button with Space/Return
Menu Bar PopoverKeyboard shortcut Cmd+H opens History window
Menu Bar PopoverKeyboard shortcut Cmd+Q quits app
Settings WindowCmd+, opens Settings from anywhere
Settings WindowArrow keys navigate between tabs
Settings WindowTab navigates through controls within each tab
Settings WindowAll toggles toggleable with Space
Settings WindowAll text fields editable with keyboard
Settings WindowAll pickers/dropdowns openable with Space and navigable with arrow keys
Overlay WindowCmd+E enters edit mode
Overlay WindowIn edit mode: Return/Enter confirms inject
Overlay WindowIn edit mode: Escape cancels edit
Overlay WindowDismiss button reachable and activatable by keyboard
Onboarding WizardAll buttons (Next, Allow Microphone, Grant Accessibility, Download Model, Done) operable by keyboard
History WindowTab to search field, type to search
History WindowArrow keys to navigate entry list
History WindowSpace/Return to select entry
History WindowKeyboard shortcut to delete selected entry (if defined)
History WindowRe-inject action reachable by keyboard
CriterionChecklist ItemPassFailN/ANotes
2.1.2Overlay Window does not trap keyboard focus — Escape dismisses it
2.1.2Onboarding Wizard does not trap focus — main app remains interactable if wizard is dismissed
2.1.2Modal confirmation dialogs (e.g., delete entry) closeable with Escape
2.1.2Settings Window closeable with Cmd+W at all times

2.4 — Navigable (WCAG 2.4.3, 2.4.7, Level AA)

Section titled “2.4 — Navigable (WCAG 2.4.3, 2.4.7, Level AA)”
SurfaceExpected Tab/Focus OrderPassFailN/ANotes
Menu Bar PopoverStatus (read-only) → Mode (read-only) → Last Transcription (read-only) → Error (read-only) → History button → Settings link → Quit button
Overlay (display mode)Header (read-only) → Content text → Edit button → Dismiss button
Overlay (edit mode)Header (read-only) → TextEditor → Cancel button → Inject button
Onboarding step 0 (Welcome)Heading → Body → Next button
Onboarding step 1 (Microphone)Heading → Description → Allow Microphone button → Next button
Onboarding step 2 (Accessibility)Heading → Description → Grant Accessibility button → Next button
Onboarding step 3 (Model Download)Heading → Description → Download button → Next button (enabled after download)
Onboarding step 4 (Completion)Heading → Description → Done button
History WindowSearch field → Filter controls → Entry list rows → Detail panel → Action buttons
Settings: GeneralUpdate channel picker → Start at login toggle → Hotkey field → …
Settings: AudioInput device picker → Sample rate → VAD threshold → …
Settings: ProcessingDefault mode picker → Language picker → …
Settings: ModelsModel list → Download/delete buttons per model
Settings: App ProfilesApp picker → Profile controls
Settings: VocabularyAdd entry field → Vocabulary list → Edit/delete per entry
Settings: LanguageLanguage picker → Dialect options
Settings: HistoryRetention count field → Age limit field → Clear history button
Settings: CommandsWake phrase field → Command list → Enable/disable toggles
Settings: PluginsPlugin list → Activate/deactivate toggles
CriterionChecklist ItemPassFailN/ANotes
2.4.7All interactive controls show a visible focus ring when navigated with keyboard (macOS default blue ring)
2.4.7Focus ring visible in Menu Bar Popover on all buttons
2.4.7Focus ring visible on Settings tab bar items
2.4.7Focus ring visible on Overlay footer buttons
2.4.7Focus ring visible on Onboarding buttons
2.4.7Focus ring visible on History list rows and action buttons
2.4.7Focus ring meets 3:1 contrast ratio against adjacent colors (WCAG 2.4.11, AA)

CriterionChecklist ItemPassFailN/ANotes
3.1.1App language matches system locale (macOS localization via Localizable.strings)
3.1.1VoiceOver uses correct language for spoken text (follows system language)

3.2 — Predictable (WCAG 3.2.1–3.2.2, Level A)

Section titled “3.2 — Predictable (WCAG 3.2.1–3.2.2, Level A)”
CriterionChecklist ItemPassFailN/ANotes
3.2.1Focusing on a Settings toggle does not automatically change its value
3.2.1Focusing on a picker does not auto-select a different option
3.2.1Focusing on the Onboarding “Download Model” button does not start download automatically
CriterionChecklist ItemPassFailN/ANotes
3.2.2Toggling the processing mode toggle does not open a new window or navigate away unexpectedly
3.2.2Changing the language picker in Settings does not trigger transcription or other side effects
3.2.2Tab switching in Settings does not produce unexpected UI changes outside the tab panel

3.3 — Input Assistance (WCAG 3.3.1–3.3.2, Level A)

Section titled “3.3 — Input Assistance (WCAG 3.3.1–3.3.2, Level A)”
CriterionChecklist ItemPassFailN/ANotes
3.3.1Errors in Settings fields (invalid hotkey, empty required field) are described in text
3.3.1Error messages identify which field caused the error
3.3.1Model download failure displays a text error, not only a color indicator
3.3.1Permission denial in Onboarding is described in text with next steps
3.3.1Transcription errors appear in Menu Bar Popover “Error:” section with text description
3.3.1Error announcements are posted via NSAccessibility.post(element:notification:) (verified in AccessibilityAuditTests.testAnnounceErrorDoesNotCrash)
CriterionChecklist ItemPassFailN/ANotes
3.3.2Every text field in Settings has a visible label (not placeholder text alone)
3.3.2Hotkey binding field has label explaining expected input format
3.3.2Vocabulary entry fields have labels (Spoken form / Replacement)
3.3.2Overlay TextEditor shows .accessibilityHint("Modify the transcription before injecting")
3.3.2Onboarding steps include explanatory body text for each permission

4.1 — Compatible (WCAG 4.1.2, 4.1.3, Level A/AA)

Section titled “4.1 — Compatible (WCAG 4.1.2, 4.1.3, Level A/AA)”
CriterionChecklist ItemPassFailN/ANotes
4.1.2All interactive controls expose their name (label) in the accessibility tree
4.1.2All interactive controls expose their role (button, textfield, checkbox, tab, etc.)
4.1.2All state-bearing controls expose their value (toggle on/off, slider value, picker selection)
4.1.2isRecording state exposed as VoiceOver announcement, not only visual change
4.1.2isProcessing state exposed as VoiceOver announcement
4.1.2Model download progress exposed as VoiceOver announcement or accessibilityValue
4.1.2Onboarding step progress exposed (“Setup progress: step N of 5”)
4.1.2Disabled controls expose disabled state (greyed appearance + isEnabled: false in tree)
4.1.2Overlay dismiss/inject buttons expose correct enabled/disabled state based on overlayText

Run Accessibility Inspector > Audit on each window to auto-detect missing names/roles/values.

Status messages that are injected dynamically (not via focus change) must be announced by assistive technology without receiving focus.

CriterionChecklist ItemPassFailN/ANotes
4.1.3”Recording started” — announced via AppState.announceRecordingStarted()NSAccessibility.post
4.1.3”Recording stopped / completed” — announced via AppState.announceRecordingCompleted()
4.1.3”Processing transcription” — announced via AppState.announceProcessing()
4.1.3”Processing complete” — announced via AppState.announceProcessingComplete()
4.1.3”Text injected” — announced (implementation to be confirmed — create task if missing)
4.1.3”Command executed” — announced (implementation to be confirmed)
4.1.3Error messages — announced via AppState.announceError(_:)
4.1.3Model download progress updates — announced periodically (not every percent)
4.1.3All NSAccessibility.post calls verified crash-free in AccessibilityAuditTests

5. Component-by-Component VoiceOver Walkthrough

Section titled “5. Component-by-Component VoiceOver Walkthrough”

Instructions: Enable VoiceOver (Cmd+F5). Navigate with VO+Arrow keys. Use VO+Space to activate. Verify each element is reached, described correctly, and activatable.

Pre-condition: App is running. VoiceOver active. Activate the VaulType menu bar item.

StepActionExpected VoiceOver OutputPassFailNotes
1Navigate to status area (idle)“Status: Ready”
1aNavigate to status area (recording)“Status: Recording”
1bNavigate to status area (processing)“Status: Processing transcription”
2Navigate to mode/language area”Mode: Clean” (or active mode name) — optionally “Language: EN”
3Navigate to Last Transcription section (if present)“Last transcription:
4Navigate to Error section (if shown)“Error:
5Navigate to History button”History, button”
5aRead History button hint”Opens the dictation history window”
5bActivate History buttonHistory window opens
6Navigate to Settings link”Settings, button”
6aRead Settings hint”Opens the VaulType settings window”
6bActivate Settings linkSettings window opens
7Navigate to Quit button”Quit VaulType, button”
7aRead Quit hint”Exits the application”
8Verify no orphaned/unlabeled elements remain after step 7No unlabeled element announced

Pre-condition: Settings window open. VoiceOver active.

StepActionExpected OutputPassFailNotes
1Navigate to tab bar”tab group” or “toolbar”
2Move to General tab”General, tab, 1 of 10” (or similar)
3Move to Audio tab”Audio, tab, 2 of 10”
4Move to Processing tab”Processing, tab, 3 of 10”
5Move to Models tab”Models, tab, 4 of 10”
6Move to App Profiles tab”App Profiles, tab, 5 of 10”
7Move to Vocabulary tab”Vocabulary, tab, 6 of 10”
8Move to Language tab”Language, tab, 7 of 10”
9Move to History tab”History, tab, 8 of 10”
10Move to Commands tab”Commands, tab, 9 of 10”
11Move to Plugins tab”Plugins, tab, 10 of 10”

Note: macOS TabView announces tabs differently based on macOS version. Acceptable variations include “selected” for the active tab. The text label must always be present.

Checklist ItemPassFailN/ANotes
Start at login toggle — label announced
Start at login toggle — state (on/off) announced
Global hotkey binding field — label announced
Update channel picker — label and current value announced
Check for updates button — label announced
Version info text — readable by VoiceOver
Checklist ItemPassFailN/ANotes
Input device picker — label “Input device” or similar announced
Input device — current selection announced
Sample rate picker — label and current value announced
VAD threshold slider — label, value, and range announced
Silence duration field — label and value announced
Checklist ItemPassFailN/ANotes
Default mode picker — label and current value announced
Processing mode descriptions — readable
Overlay enable toggle — label and state announced
Overlay auto-inject delay field — label and value announced
Checklist ItemPassFailN/ANotes
Model list — each row announces model name and download state
Download button per model — label includes model name
Delete button per model — label includes model name
Download progress — announced via VoiceOver (percentage or status text)
Default model indicator — announced
Checklist ItemPassFailN/ANotes
App picker — label announced
Selected app name announced
Per-app overrides (mode, vocabulary) — labels announced
Add profile button — label announced
Delete profile button — label includes app name
Checklist ItemPassFailN/ANotes
”Spoken form” field — label announced
”Replacement” field — label announced
Add entry button — label announced
Vocabulary list — each row announces spoken form and replacement
Edit/delete buttons per entry — include entry name in label
Checklist ItemPassFailN/ANotes
Language picker — label and current selection announced
Auto-detect language toggle — label and state announced
Dialect/region picker (if present) — label and value announced
Checklist ItemPassFailN/ANotes
Retention count field — label and value announced
Age limit field — label and value announced
Favorites exemption toggle — label and state announced
Clear history button — label announced
Factory reset button — label announced
Destructive action confirmation dialog — announced before execution
Checklist ItemPassFailN/ANotes
Wake phrase field — label and current value announced
Command list — each row announces command name and enabled state
Enable/disable toggle per command — label includes command name
Add custom command button — label announced
Custom command name field — label announced
Action steps list — readable
Checklist ItemPassFailN/ANotes
Plugin list — each row announces plugin name and activate/deactivate state
Activate/deactivate button per plugin — includes plugin name in label
”No plugins installed” empty state — announced
Plugin directory path — readable

5.3 — Overlay Window (OverlayContentView)

Section titled “5.3 — Overlay Window (OverlayContentView)”

Pre-condition: A dictation has been completed with overlay enabled. Overlay window is visible. VoiceOver active.

StepActionExpected VoiceOver OutputPassFailNotes
1Navigate to headerMode name + optionally language (e.g., “Clean, EN”)
2Navigate to processing indicator (when processing)“Processing…” or “Transcribing…“
3Navigate to transcription text (display mode)Full transcription text announced
4Navigate to Edit button (display mode)“Edit transcription, button”
4aRead Edit button hint”Opens the text editor to modify the transcription before injecting”
4bActivate Edit buttonEdit mode activated; TextEditor receives focus automatically
5In edit mode — TextEditor”Edit transcription text, text editor,
5aRead TextEditor hint”Modify the transcription before injecting”
6Navigate to Cancel button (edit mode)“Cancel edit, button”
6aRead Cancel hint”Discards your edits and cancels injection”
6bActivate CancelReturns to display mode; edit cancelled
7Navigate to Inject button (edit mode)“Inject edited text, button”
7aRead Inject hint”Types your edited text at the cursor position”
7bActivate InjectText injected; overlay dismisses
8Navigate to Dismiss button (display mode)“Dismiss, button” (or “Dismiss overlay”)
8aActivate DismissOverlay dismisses
9Empty state — navigate to content area”Waiting for transcription…“

5.4 — Onboarding Wizard (OnboardingView)

Section titled “5.4 — Onboarding Wizard (OnboardingView)”

Pre-condition: Fresh install or onboarding reset. Onboarding window visible. VoiceOver active.

StepExpected VoiceOver OutputPassFailNotes
Progress indicator”Setup progress: step 1 of 5”
Step heading”Welcome to VaulType” (or similar)
Step bodyDescription text read
Next button”Next, button”
StepExpected VoiceOver OutputPassFailNotes
Progress indicator”Setup progress: step 2 of 5”
Step headingMicrophone permission heading
Permission explanationBody text read fully
Allow Microphone button”Allow Microphone Access, button” (or similar)
Permission granted stateSome visual or textual acknowledgment announced
Next button (enabled after grant)“Next, button”
StepExpected VoiceOver OutputPassFailNotes
Progress indicator”Setup progress: step 3 of 5”
Explanation of accessibility useBody text read
Open System Settings buttonButton label announced
Permission granted stateConfirmed via text
StepExpected VoiceOver OutputPassFailNotes
Progress indicator”Setup progress: step 4 of 5”
Model name/descriptionText read
Download button”Download, button”
Download in progressProgress announced (via VoiceOver notification or status text)
Download completeCompletion state announced or text changes
Next button (enabled after download)“Next, button”
StepExpected VoiceOver OutputPassFailNotes
Progress indicator”Setup progress: step 5 of 5”
Completion headingRead
Completion bodyRead
Done button”Done, button” or “Get Started, button”
Activate DoneOnboarding dismissed; main app usable

Pre-condition: History window open. Some dictation entries exist. VoiceOver active.

StepActionExpected VoiceOver OutputPassFailNotes
1Navigate to search field”Search, text field”
1aType in searchResults update; VoiceOver announces count change or updates list
2Navigate to filter controls (app picker)“Filter by app, popup button, All”
3Navigate to mode filter picker”Filter by mode, popup button, All”
4Navigate to Favorites Only toggle”Favorites only, checkbox, off”
5Navigate to date range fields (if present)“From date” / “To date” announced
6Navigate to entry list”dictation entries, list” or equivalent
6aNavigate to first entry, ,
6bNavigate to next entrySecond entry details announced
7Select an entryDetail panel opens; VoiceOver shifts to detail
7aDetail panel — full textText announced or readable
7bDetail panel — Copy button”Copy, button” or “Copy text, button”
7cDetail panel — Re-inject button”Re-inject, button” or similar with hint
7dDetail panel — Favorite toggle”Favorite, checkbox, off”
7eDetail panel — Delete button”Delete, button” + confirmation dialog
8Delete confirmation dialog”Are you sure? Delete / Cancel” announced
8aCancel in dialogDialog dismissed; entry preserved
9Edit-and-reinject flowEdit field focuses; Inject button announced
10Empty state (no entries)“No dictation history” or similar announced
11Empty search results”No results” or similar announced

Tab order must follow the logical reading order of the layout (top-to-bottom, left-to-right for LTR locales). Verify with keyboard-only navigation (no mouse).

Enable Full Keyboard Access in System Settings > Keyboard before testing.

Window / ViewFirst Focusable ElementLast Focusable ElementTab Cycles CorrectlyPassFail
Menu Bar PopoverHistory buttonQuit buttonYes — wraps to History
Settings WindowTab bar item 0 (General)Last control on active tabYes
Overlay (display)Edit buttonDismiss buttonYes
Overlay (edit)TextEditorInject buttonYes
OnboardingFirst button on current stepNavigation button (Next/Done)Yes
History WindowSearch fieldLast action button on selected entryYes
ShortcutActionSurfacePassFailNotes
Cmd+,Open SettingsApp-wide
Cmd+HOpen History windowMenu Bar Popover
Cmd+QQuit VaulTypeMenu Bar Popover
Cmd+EEnter edit modeOverlay
ReturnConfirm injectOverlay (edit mode)
EscapeCancel edit / close overlayOverlay
Cmd+WClose Settings windowSettings Window
Cmd+FFocus search (if implemented)History Window
Arrow keysNavigate tab barSettings Window
Arrow keysNavigate list entriesHistory Window
SpaceToggle checkbox/toggleSettings tabs
Checklist ItemPassFailN/ANotes
Default hotkey (fn) documented in Settings > General
Alternative hotkeys configurable for users who cannot press fn
Hotkey does not conflict with system VoiceOver shortcuts
Hotkey works when VoiceOver is active
Custom shortcut aliases (phrase → keyboard shortcut injection) operable without mouse

Verify that each app state change is announced to assistive technology via NSAccessibility.post(element:notification:) in AppState. Reference implementation: VaulTypeTests/AccessibilityAuditTests.swift.

State ChangeAnnouncement MethodExpected Spoken Text (approximate)Automated TestPassFail
Recording startedannounceRecordingStarted()”Recording started”testAnnounceRecordingStartedDoesNotCrash
Recording stoppedannounceRecordingCompleted()”Recording stopped”testAnnounceRecordingCompletedDoesNotCrash
Processing startedannounceProcessing()”Processing transcription”testAnnounceProcessingDoesNotCrash
Processing completeannounceProcessingComplete()”Processing complete”testAnnounceProcessingCompleteDoesNotCrash
Error occurredannounceError(_:)”Error: testAnnounceErrorDoesNotCrash
Text injectedCustom announcement (verify exists)“Text injected”Create test if missing
Command executed (success)Custom announcement (verify exists)“Command executed: Create test if missing
Command failedannounceError(_:)”Command failed: testAnnounceErrorDoesNotCrash
Model download progressPeriodic announcement (verify exists)“Downloading model: N percent”Create test if missing
Model download completeCustom announcement (verify exists)“Model download complete”Create test if missing
Onboarding step changeImplicit (focus shifts to new step content)Focus on new heading reads itN/A
Settings saved/appliedCustom announcement (verify exists)“Settings saved”Create test if missing

Note on gaps: Announcements for “Text injected”, “Command executed”, “Model download progress”, “Model download complete”, and “Settings saved” are marked as “verify exists” because their implementation was not confirmed in the reviewed source files at audit time. If these announcements are absent, create DevTrack tasks to add them and re-audit those rows.


Enable System Settings > Accessibility > Display > Increase Contrast before testing.

Code reference: AppState.prefersHighContrast mirrors NSWorkspace.shared.accessibilityDisplayShouldIncreaseContrast. The overlay border width increases to 2px and opacity increases to 0.5 when prefersHighContrast is true (OverlayContentView line 51–55).

ComponentChecklist ItemPassFailN/ANotes
Overlay borderBorder visible at 2px / 0.5 opacity when contrast enabled
Overlay borderColour contrast of border against window bg ≥ 3:1
Menu Bar status textForeground/background contrast ≥ 4.5:1 in high contrast mode
Settings controlsAll labels, toggles, pickers readable in high contrast
History entriesText remains readable against row background in high contrast
Onboarding buttonsButton fills and text readable in high contrast
Focus ringsFocus ring visible and contrasting against all backgrounds
Active recording indicatorRed circle distinguishable from background in high contrast
Error text (orange)Orange text remains distinguishable in high contrast
Language badgeAccent colour badge readable in high contrast
Inject button (borderedProminent)Blue fill + white text readable in high contrast

Enable System Settings > Accessibility > Motion > Reduce Motion before testing.

Code reference: AppState.prefersReducedMotion mirrors NSWorkspace.shared.accessibilityDisplayShouldReduceMotion. OnboardingView reads @Environment(\.accessibilityReduceMotion). HistoryView also reads accessibilityReduceMotion.

ComponentChecklist ItemPassFailN/ANotes
Onboarding step transitionsCross-fade or instant switch (no slide animation) when reduce motion enabled
Onboarding progress dotsDot fill changes instantly (no spring animation)
History list insertionsRows appear without slide-in animation
Overlay appearanceOverlay appears without scale/fade animation
ProgressView spinnersSpinners exempt — they are functional indicators, not decorative
Menu Bar popover open/closeUses system default (controlled by macOS)
Any custom spring/bounce animationsMust check prefersReducedMotion / accessibilityReduceMotion and remove animation

Enable System Settings > Accessibility > Display > Reduce Transparency before testing.

Code reference: AppState.prefersReducedTransparency mirrors NSWorkspace.shared.accessibilityDisplayShouldReduceTransparency. OverlayContentView uses appState.prefersReducedTransparency to switch between .ultraThinMaterial and solid NSColor.windowBackgroundColor. OverlayWindow.applyTransparencyPreference(appState:) sets isOpaque = true and backgroundColor = .windowBackgroundColor when pref is active.

ComponentChecklist ItemPassFailN/ANotes
Overlay backgroundSolid background applied instead of material when reduce transparency enabled
Overlay opacityOverlayWindow.isOpaque == true when reduce transparency enabled (verified by testOverlayWindowIsOpaqueWhenReducedTransparencyActive)
Text contrast on overlaySolid bg ensures text contrast ≥ 4.5:1
Menu Bar PopoverSystem handles transparency; no custom material to override
Settings WindowStandard NSWindow; no custom material

Use this table to record the outcome of each audit run. One row per WCAG success criterion.

WCAG SCLevelCriterionStatusDate TestedTesterNotes
1.1.1ANon-text Content
1.3.1AInfo and Relationships
1.3.2AMeaningful Sequence
1.3.3ASensory Characteristics
1.4.1AUse of Color
1.4.3AAContrast (Minimum)
1.4.4AAResize Text
2.1.1AKeyboard
2.1.2ANo Keyboard Trap
2.4.3AFocus Order
2.4.7AAFocus Visible
3.1.1ALanguage of Page
3.2.1AOn Focus
3.2.2AOn Input
3.3.1AError Identification
3.3.2ALabels or Instructions
4.1.2AName, Role, Value
4.1.3AAStatus Messages

Status values: Pass | Fail | Partial | N/A | Not Tested


Section titled “10. Known Issues and Recommended Follow-Up Tasks”

The following gaps were identified during the initial audit document creation based on code review. Create DevTrack tasks for each before the next audit cycle.

IssueSeverityAffected SurfaceRecommended Action
”Text injected” VoiceOver announcement not confirmed in codeHighOverlay / Injection pipelineAdd announceStateChange("Text injected") call in TextInjectionService after successful injection
”Command executed” announcement not confirmedHighCommands pipelineAdd announcement in CommandExecutor on success and failure paths
Model download progress announcement not confirmedMediumModels tabAdd periodic announceStateChange calls in ModelManager download handler
”Settings saved” announcement not confirmedLowSettings WindowAdd announcement when settings are persisted
Overlay header mode/language area lacks .accessibilityLabelMediumOverlay WindowAdd combined accessibility label to the header HStack in OverlayContentView
Overlay processing ProgressView in header lacks accessible textMediumOverlay WindowAdd .accessibilityElement(children: .combine) + label to processing HStack in headerBar
”Waiting for transcription…” text uses .tertiary — contrast unconfirmedMediumOverlay WindowMeasure .tertiary vs material background; adjust if below 4.5:1
Settings per-tab audits require running app — cannot fully audit staticallyMediumAll Settings tabsPerform live VoiceOver walkthrough for each tab at release milestone
Vocabulary/Commands/App Profiles dynamic list item labels not verifiedMediumSettings tabsAudit that dynamically generated list rows include item names in button labels
Custom keyboard shortcut injection may conflict with VoiceOver shortcutsHighCommands / InjectionTest shortcut injection while VoiceOver is active; add conflict documentation

VersionDateAuthorChanges
1.02026-02-20Initial audit document creationCreated from code review of Phase 5 codebase