Skip to content

PATTERN Cited by 1 source

Accessibility delegate override for semantic fix

Pattern

Accessibility delegate override for semantic fix is the Android-platform pattern of correcting framework-level accessibility semantics by attaching a subclass of AccessibilityDelegate (or overriding the view's delegate hook) rather than changing the underlying widget tree. The delegate's responsibility is to rewrite what the accessibility framework sees — element counts, roles, actions, state — so assistive tech like TalkBack receives semantically correct information even when the raw widget structure contains decorative or non-semantic children.

Why the pattern exists

UI component libraries commonly include decorative elements (separators, dividers, spacers, legend rows, section headers) that are visible but not semantically list items. The Android a11y framework defaults to reading the adapter's raw child count into AccessibilityNodeInfoCompat.CollectionInfo, so TalkBack announces an inflated list size. Rewriting the widget tree to remove decoration would regress the visual design; the delegate pattern lets the a11y semantics diverge from the widget tree.

The same mechanism applies to a broader class of corrections: marking an element as a heading, exposing a custom role, correcting a misleading default state description, or adding custom actions (see patterns/custom-talkback-actions-as-gesture-alternative).

Canonical instance: Slack Kit SKListAccessibilityDelegate

From Slack's 2025-11-19 VPAT post:

Our vintage Slack Kit (SK) Bottom sheet was the primary source of the issue. SK divider, although purely decorative, was considered an item within a list. For example, if the bottom sheet had 7 row items, including 2 divider items, TalkBack would announce "7 items in a list" instead of the intended "5 items in a list".

To resolve this, we introduced a new SKListAccessibilityDelegate for SKListAdapter, overwriting the a11y CollectionInfo with the correct number of items.

The fix lives at the component-library layer (Slack Kit), not at each screen using the component — which means every consumer of SKListAdapter picks up the correct count for free.

Structure

  1. Identify the symptom — TalkBack announcing something semantically wrong (wrong count, wrong role, missing action).
  2. Identify the underlying framework default that produced the wrong semantics (raw adapter count, default view role, etc.).
  3. Subclass AccessibilityDelegate (or RecyclerView.ItemDelegate / RecyclerViewAccessibilityDelegate variants depending on the widget).
  4. Override the specific hook — onInitializeAccessibilityNodeInfo, onPopulateAccessibilityEvent, getCollectionInfo, etc.
  5. Attach the delegate at component-library layer if the symptom is generic, not at each screen.
  6. Cover the fix with an automated a11y regression (Axe-in- Playwright for web; Espresso + AccessibilityChecks on Android; manual smoke with TalkBack turned on).

When this pattern is the right tool

  • The UI correctly models visual hierarchy but the a11y framework's default semantic extraction is wrong.
  • The fix should apply to every consumer of a shared component.
  • Changing the widget tree would regress visual design or performance.

When to avoid

  • If the widget tree itself is wrong (e.g. sem-level siblings with no container), fix the tree; delegate overrides propagate hacks.
  • If the correction is one-off for a specific screen, inlining the delegate at that screen is fine.
  • If the platform provides a first-class attribute for the need (e.g. android:importantForAccessibility="no" for pure decoration), prefer that over a delegate override.

Generalisation

The pattern generalises to any assistive-tech API where the platform provides a delegate or decorator surface for rewriting the semantic layer:

  • iOS VoiceOverUIAccessibility informal protocol on UIView / UIAccessibilityContainer.
  • Web ARIAaria-* attribute overrides on DOM nodes.
  • DesktopUIAutomation (Windows), NSAccessibility (macOS) delegate protocols.

Each platform has a different surface, but the shape — rewrite the semantic layer without touching the rendering layer — is the same.

Seen in

Last updated · 470 distilled / 1,213 read