diff options
| author | Christian Cleberg <[email protected]> | 2026-07-20 21:49:57 -0500 |
|---|---|---|
| committer | Christian Cleberg <[email protected]> | 2026-07-20 21:57:22 -0500 |
| commit | e2da09fec3d3d52ac6108b9256b2419405f76dd3 (patch) | |
| tree | f94c68534b3f8823f1c07c831bbd8be2873b4807 /Docs | |
| parent | 1c2830fbfc8cccfc0c585ec2356467bb95bc1642 (diff) | |
| download | domain-dig-e2da09fec3d3d52ac6108b9256b2419405f76dd3.tar.gz domain-dig-e2da09fec3d3d52ac6108b9256b2419405f76dd3.tar.bz2 domain-dig-e2da09fec3d3d52ac6108b9256b2419405f76dd3.zip | |
feat(a11y): Dynamic Type reflow and tap targets (#21 phase 3)
Takes the audit from 18 findings to 11 in dark mode. Everything that
remains is system-rendered or placeholder noise, characterised below.
The largest win was not where the plan expected. Every empty-state
heading reported as clipped text, and the cause was `Label`: it
constrains its own title, and `.fixedSize` applied to the Label does not
reach the `Text` inside. Splitting into `HStack { Image; Text }` and
putting the modifier on the Text cleared all four empty states at both
default and accessibility sizes.
That fix then caused a regression the audit caught immediately. `Label`
folds its image into the title's accessibility element; an HStack does
not, so the icon began announcing its raw SF Symbol name
("checklist.unchecked") to VoiceOver. Decorative icons split out of a
Label now carry .accessibilityHidden(true).
Tap targets:
- AppCopyButton was a literal 30x30 on nearly every data row. Now
@ScaledMetric from 44, floored at AppLayout.minimumTapTarget —
@ScaledMetric scales down below the default text size as well as up,
so the floor is load-bearing.
- controlMinHeight was 42 in compact density, putting every collapsible
section header and both Run buttons under the minimum.
Reflow:
- CardView's allowsHorizontalScroll defaulted to true, so nine call sites
hid content behind a horizontal gesture instead of wrapping — a WCAG
1.4.10 failure and the mechanism behind clipped rows at large text
sizes. The default is now false, and the remaining opt-in is suppressed
at accessibility sizes.
- Fixed .system(size:) point sizes replaced with text styles in the app
and the widget.
- The widget is clamped at accessibility1, the one place clamping is
correct: a widget canvas is a fixed size and WidgetKit truncates
overflow with no scroll affordance.
Two hypotheses were tested and discarded rather than left in. Monospaced
fonts looked like the clipping culprit — the app is 82% monospaced and
hyphenates mid-word at accessibility sizes — but switching the empty
state to proportional changed nothing, and prose typography is a design
decision rather than an accessibility fix. Shortening search prompts and
the domain placeholder also changed nothing: placeholder text is reported
clipped regardless of length, so "Search" is flagged exactly as "Search
portfolio" was.
Not done: ViewThatFits reflow for BatchResultRowView and
WatchlistRowView. Those rows never render in the audit because the test
simulator has no tracked domains or batch results, so any change there
would be unverifiable. Absence of findings is absence of data.
Diffstat (limited to 'Docs')
| -rw-r--r-- | Docs/ACCESSIBILITY.md | 21 |
1 files changed, 21 insertions, 0 deletions
diff --git a/Docs/ACCESSIBILITY.md b/Docs/ACCESSIBILITY.md index 40acc1c..6032bf6 100644 --- a/Docs/ACCESSIBILITY.md +++ b/Docs/ACCESSIBILITY.md @@ -187,6 +187,27 @@ Pre-push rather than pre-commit deliberately: the suite takes ~85s, and at pre-commit that blocks every commit. A hook routinely bypassed with `--no-verify` is worse than no hook, because it trains you to ignore it. +## Layout gotchas found the hard way + +- **`Label` clips its own title.** Every empty-state heading reported as clipped + text. `.fixedSize` applied to the `Label` does not reach the `Text` inside it, + so the fix is to split it into an `HStack { Image; Text }` and put the modifier + on the `Text`. Changing the font design did **not** help — that hypothesis was + tested and discarded. +- **Splitting a `Label` exposes its icon to VoiceOver.** `Label` folds the image + into the title's accessibility element; an `HStack` does not, so the icon + starts announcing its raw SF Symbol name ("checklist.unchecked"). Decorative + icons split out of a `Label` need `.accessibilityHidden(true)`. +- **Placeholder text is always reported as clipped.** Search prompts and + `TextField` placeholders are flagged regardless of length — shortening + "Search portfolio" to "Search" changed nothing. Treat `textClipped` findings on + a `searchField` or `textField` element as noise rather than shortening useful + prompts to chase them. +- **`AppLayout.minimumTapTarget` is the floor for every control.** `@ScaledMetric` + scales *down* below the default text size as well as up, so a scaled dimension + needs `max(scaled, AppLayout.minimumTapTarget)` or it drops under 44pt for + users who prefer smaller text. + ## Notes - **Disabled controls are a false positive, and are suppressed.** WCAG 1.4.3 |
