Summary
A blind user cannot complete wallet backup. Not "with difficulty" — the seed-phrase verification step gives no feedback a screen reader can perceive, and the only reliably announced control on that screen is Skip.
Found during the VoiceOver audit for support ticket 32072. Not addressed by #1096, which fixed the shared components, because the fix here needs a product decision first.
What is broken
The "I wrote it down" gate — DashWallet/Sources/UI/Setup/SecureWallet/Seed/Views/DWCheckbox.m:137-174
A custom control that exposes no role and no checked state. VoiceOver announces nothing about whether it is ticked, and it blocks Continue (DWPreviewSeedPhraseViewController.m:150-153).
The tap-the-words-in-order verification — DashWallet/Sources/UI/Setup/SecureWallet/Seed/Views/SeedPhrase/DWSeedWordView.m:107-158
The word chips are custom UIControls with no trait, no state and no position information. A wrong word is rejected with a red flash and a shake only (DWSeedPhraseView.m:328-347) — nothing is announced, so the user gets no signal that anything happened.
Related, same flow — the seed screen auto-pops on applicationWillResignActive (DWPreviewSeedPhraseViewController.m:183-185). VoiceOver users hit interruptions more often than sighted users, so they get ejected mid-read more often.
Why it matters
The flow funnels blind users into holding funds against a seed phrase they never verified and may never have heard in full. That is a lockout risk, not a convenience issue.
The decision needed before implementing
Should VoiceOver read the recovery phrase aloud at all?
The same screen already treats the phrase as sensitive — a screenshot of it wipes the wallet. Reading twelve words aloud through a device speaker is a comparable exposure, and it is not obvious that the answer is simply "yes".
Plausible options, none of them free:
- Announce the words normally. Simplest, and the only option that makes the flow usable unaided — but it speaks the phrase aloud in whatever room the user is in.
- Announce only with headphones connected, and say why when they are not.
- Announce positions and state ("word 3 of 12, selected") without speaking the word itself. Preserves secrecy but makes verification unusable, since the user cannot tell which word they are picking.
This is a call for the owner / CTO, not something to settle in a PR.
Scope once decided
DWCheckbox — role, state, and a state-change announcement
DWSeedWordView — button trait, selected state, position in the sequence
DWSeedPhraseView — an announcement on rejection, replacing shake-only feedback
DWPinView.m:189-199 — PIN mismatch during setup is currently haptic + shake + a silent clear; same class of defect, same flow
Estimated 20-25 engineer-hours for the Setup zone as a whole, mostly mechanical once the policy question is answered.
Context
Note the static rules do not catch these — they are ObjC custom controls, not the icon-only-button pattern the audit rules recognise. The tooling under-reports; this was found by reading.
Summary
A blind user cannot complete wallet backup. Not "with difficulty" — the seed-phrase verification step gives no feedback a screen reader can perceive, and the only reliably announced control on that screen is Skip.
Found during the VoiceOver audit for support ticket 32072. Not addressed by #1096, which fixed the shared components, because the fix here needs a product decision first.
What is broken
The "I wrote it down" gate —
DashWallet/Sources/UI/Setup/SecureWallet/Seed/Views/DWCheckbox.m:137-174A custom control that exposes no role and no checked state. VoiceOver announces nothing about whether it is ticked, and it blocks Continue (
DWPreviewSeedPhraseViewController.m:150-153).The tap-the-words-in-order verification —
DashWallet/Sources/UI/Setup/SecureWallet/Seed/Views/SeedPhrase/DWSeedWordView.m:107-158The word chips are custom
UIControls with no trait, no state and no position information. A wrong word is rejected with a red flash and a shake only (DWSeedPhraseView.m:328-347) — nothing is announced, so the user gets no signal that anything happened.Related, same flow — the seed screen auto-pops on
applicationWillResignActive(DWPreviewSeedPhraseViewController.m:183-185). VoiceOver users hit interruptions more often than sighted users, so they get ejected mid-read more often.Why it matters
The flow funnels blind users into holding funds against a seed phrase they never verified and may never have heard in full. That is a lockout risk, not a convenience issue.
The decision needed before implementing
Should VoiceOver read the recovery phrase aloud at all?
The same screen already treats the phrase as sensitive — a screenshot of it wipes the wallet. Reading twelve words aloud through a device speaker is a comparable exposure, and it is not obvious that the answer is simply "yes".
Plausible options, none of them free:
This is a call for the owner / CTO, not something to settle in a PR.
Scope once decided
DWCheckbox— role, state, and a state-change announcementDWSeedWordView— button trait, selected state, position in the sequenceDWSeedPhraseView— an announcement on rejection, replacing shake-only feedbackDWPinView.m:189-199— PIN mismatch during setup is currently haptic + shake + a silent clear; same class of defect, same flowEstimated 20-25 engineer-hours for the Setup zone as a whole, mostly mechanical once the policy question is answered.
Context
AUDIT-SUMMARY.md)scripts/a11y_audit.py(ci(ui): add a VoiceOver audit tool and an accessibility CI ratchet #1091)Note the static rules do not catch these — they are ObjC custom controls, not the icon-only-button pattern the audit rules recognise. The tooling under-reports; this was found by reading.