Skip to content

fix(hig): name the controls that were silent to VoiceOver - #2969

Merged
datlechin merged 1 commit into
mainfrom
fix/accessibility-labels
Sep 18, 2026
Merged

datlechin merged 1 commit into
mainfrom
fix/accessibility-labels

Conversation

@datlechin

Copy link
Copy Markdown
Member

An audit of the app's SwiftUI and AppKit surfaces against Apple's accessibility guidance. 34 controls fixed, every one read in context first.

The pattern behind most of it

.help() was being used as if it were a label. On macOS .help() sets the tooltip and the accessibility hint. VoiceOver announces the label first and reads hints only after a delay, if the user has not switched them off. So a button carrying a perfectly good localized string in .help() and nothing in .accessibilityLabel() announces its SF Symbol name, or nothing.

23 icon-only controls were in exactly that state: Open in Window on five different cell and field editors, Remove filter, Close parameter panel, Add Row, Delete Selected, Refresh Now, Copy install command, Bulk actions, Previous/Next Match, Replace, Replace All, Done, and more. The strings already existed and were already localized, so the fix is additive and the tooltip is unchanged.

What was worse than a missing label

  • Two controls had no name at all. The find bar's clear button (PanelTextField) had neither a label nor a tooltip. The linked-folder enable switch has EmptyView() as its label plus .labelsHidden(), so nothing named which folder it toggles; it now takes the folder's name.
  • Foreign key picker rows could only be chosen with a mouse. Committing a row happened in onTapGesture, which no assistive technology can reach, and choosing a row is the picker's whole purpose. The rows now carry .isButton and an accessibilityAction.
  • Query Plan columns never said how they were sorted. Every column there gets a sortDescriptorPrototype, so all of them are click-sortable. NSTableView.sortDescriptors paints the arrow but reaches no accessibility client, which SortableHeaderView already knows and handles; the plan outline did not. It now calls setAccessibilitySortDirection on the sorted column and clears the rest.
  • Four standalone icons carried meaning no text repeated: the filter preset's missing-column warning, the grant-option marker, the array editor's drifted-value warning, and the endpoint picker's checkmark.
  • The filter bar's column and operator popups were Picker("", …) plus .labelsHidden(), so each filter row offered two anonymous popups.

No new catalog keys: "Filter column", "Filter operator" and "Selected" were all already there.

Six reported defects that were not defects

Worth recording, because the scan that found them will find them again:

  • FavoriteStarButton is .accessibilityHidden(true) on purpose; the row combines its children and exposes the toggle through FavoriteAccessibilityAction.
  • Three icons sit inside a Label { Text(…) } icon: { … }, which is already named by its title: the compare plan warning, the activity-log outcome and the copy-objects error.
  • Two sidebar rows already hide their icons and compose an explicit label; LinkedFavoriteRowView even folds the file's encoding into it, so the non-UTF-8 warning is spoken.

Deliberately not done

The audit also counted 107 decorative icons that are not .accessibilityHidden(true). I fixed none of them. Doing five of 107 is worse than doing zero: it reads as inconsistent, and each misplaced .accessibilityHidden(true) deletes the only name a control has. That needs one sweep with each site read, as its own change.

Verification

  • App scheme: BUILD SUCCEEDED.
  • swiftlint lint --strict over all 27 changed Swift files: 0 violations.
  • 5 new cases, all passing. Two of them are behavioural rather than source scans: they drive a real NSOutlineView through the coordinator and read accessibilitySortDirection() back off the header cells.
  • Negative control run: stubbing out the setAccessibilitySortDirection call makes both behavioural cases fail, so they are testing the fix and not the test.

@datlechin
datlechin merged commit 170a77b into main Sep 18, 2026
9 of 13 checks passed
@datlechin
datlechin deleted the fix/accessibility-labels branch September 18, 2026 05:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant