Accessibility adapters
Spec version: 1.0.0-draft (see Overview)
This chapter is informative. It provides guidance for platform adapter repos; it does not add normative constraints to the foundation spec.
This chapter shows how the semantic accessibility vocabulary defined in Accessibility maps to real platform APIs across web (ARIA), iOS (UIAccessibility), Android (AccessibilityNodeInfo), and voice/multimodal surfaces. Platform adapter repos use this as a translation contract when implementing accessibility support for Spectrum components. The foundation layer ships intent; adapters ship the platform binding.
Scoped under planned RFC-B. See rfc-coordination.md.
Adapter overview
A platform adapter repo declares which foundation components it supports and implements a mapping layer that translates each component's accessibility declaration to the platform's native accessibility API. When a field is absent from a component's accessibility declaration, the adapter falls back to its own defaults or omits the attribute.
Adapters are not required to implement every field — a minimal adapter might only handle role and focusable. The fields documented below describe what each foundation field means at the platform layer when the adapter chooses to implement it.
Web / ARIA
ARIA (Accessible Rich Internet Applications) is the primary web accessibility API. Adapters set HTML role attributes and aria-* state properties on the root element or on relevant sub-elements.
role
Foundation role |
ARIA / HTML equivalent |
|---|---|
button |
<button> or role="button" |
checkbox |
<input type="checkbox"> or role="checkbox" |
combobox |
role="combobox" |
dialog |
role="dialog" |
link |
<a href> or role="link" |
listbox |
role="listbox" |
menu |
role="menu" |
menuitem |
role="menuitem" |
radio |
<input type="radio"> or role="radio" |
slider |
role="slider" |
spinbutton |
role="spinbutton" |
switch |
role="switch" |
tab |
role="tab" |
textbox |
<input type="text"> or role="textbox" |
tooltip |
role="tooltip" |
tree |
role="tree" |
progressbar |
role="progressbar" |
meter |
role="meter" |
grid |
role="grid" |
listitem |
<li> or role="listitem" |
group |
role="group" |
intents
| Foundation intent | Web guidance |
|---|---|
trigger |
Ensure the element has an accessible name via aria-label, aria-labelledby, or a visible label |
select |
Set aria-selected on child options; the container gets role="listbox" or role="combobox" |
navigate |
Use <a href> or role="link"; ensure the destination is announced |
expand |
Set aria-expanded="true" / "false" on the trigger element |
collapse |
Toggle aria-expanded to "false" |
input |
Associate a visible <label> or aria-label; use aria-describedby for hints |
choose |
Use aria-valuenow, aria-valuemin, aria-valuemax for range inputs |
dismiss |
Return focus to the trigger element on close; announce the closure if needed |
focusable
true→ settabindex="0"on the root element, or use a natively focusable HTML element.false→ settabindex="-1"for elements that receive programmatic focus; usearia-hidden="true"for purely decorative elements. For roving-tabindex composites (radio groups, toolbars, tree views), the adapter manages which child holdstabindex="0".
keyboardIntents
| Foundation intent | Conventional key binding |
|---|---|
activate |
Enter, Space |
expand |
ArrowDown, Enter, Space |
collapse |
Escape, ArrowUp |
navigate-options |
ArrowUp, ArrowDown |
navigate-items |
ArrowLeft, ArrowRight |
increment |
ArrowUp, ArrowRight |
decrement |
ArrowDown, ArrowLeft |
dismiss |
Escape |
select-all |
Ctrl+A (Windows/Linux), Cmd+A (macOS) |
Key bindings shown are conventional. Web adapters should follow the ARIA Authoring Practices Guide patterns for the component's role.
wcag
The wcag array is developer guidance at the web layer, not an automated attribute. Web adapter implementations should:
- Include listed criteria in component documentation.
- Add automated accessibility tests (e.g., axe-core) that target listed criteria.
- Flag in PR checklists when changes may affect listed criteria.
State fields
announce→ setaria-live="polite"(or"assertive"for urgent transitions) on a live region element and inject theannouncetext on state entry.communicates→ map to the correspondingaria-*state attribute (e.g.,"expanded"→aria-expanded="true"). See thecommunicatesvocabulary table in Accessibility.blocksInteraction→ setaria-disabled="true"andtabindex="-1"on the root element; suppress pointer and keyboard events.
iOS / UIAccessibility
iOS exposes accessibility semantics through UIAccessibility protocol properties on UIView (UIKit) and through accessibility modifiers in SwiftUI.
role
Foundation role |
UIAccessibilityTraits / SwiftUI |
|---|---|
button |
.button |
checkbox |
.button with toggled state, or SwiftUI Toggle |
combobox |
.button (picker-like behavior) |
dialog |
Presented as modal sheet; VoiceOver reads the accessible label |
link |
.link |
listbox |
Container view; children use .button or .selected |
menu |
.button on trigger; menu items as .button |
menuitem |
.button |
radio |
.button with .selected when active |
slider |
.adjustable |
spinbutton |
.adjustable |
switch |
.button with .selected / SwiftUI Toggle |
tab |
.button with .selected |
textbox |
.keyboard traits; UITextField or UITextView |
tooltip |
.staticText on an overlay element |
tree |
Hierarchical cells with accessibilityContainerType |
progressbar |
.updatesFrequently trait; accessibilityValue carries percent or description |
meter |
.staticText with accessibilityValue describing the current measurement |
grid |
accessibilityContainerType = .dataTable |
listitem |
Children of an accessibility container; .staticText trait on leaf items |
group |
accessibilityContainerType = .semanticGroup |
intents
trigger→ provideaccessibilityLabeldescribing the action the user will invoke.select→ setaccessibilityTraitsto include.selectedwhen the item is in a selected state.expand/collapse→ updateaccessibilityHintoraccessibilityValueto describe the current expanded/collapsed state.choose→ implementaccessibilityIncrement()/accessibilityDecrement()with the.adjustabletrait.dismiss→ handleaccessibilityPerformEscape()to close the view and return focus.
focusable
true→isAccessibilityElement = true.false→isAccessibilityElement = false. For composite views that manage focus internally (e.g., a radio group), setaccessibilityContainerTypeand manage the focused child programmatically.
keyboardIntents
Relevant for external keyboard support (iPad with Magic Keyboard, Bluetooth keyboard):
activate→ register aUIKeyCommandfor Return and Space.navigate-options/navigate-items→ registerUIKeyCommandentries for arrow keys.dismiss→ register aUIKeyCommandfor Escape.
wcag
Include WCAG criteria in component documentation and Accessibility Inspector audits. The wcag array has no automatic mapping at the iOS layer.
State fields
announce→ callUIAccessibility.post(notification: .announcement, argument: announceText)on state entry. In SwiftUI, useAccessibilityNotification.Announcement.communicates→ set the corresponding trait or value (e.g.,"expanded"→ setaccessibilityValue = "expanded"/"collapsed";"disabled"→ add.notEnabled;"busy"→ add.causesPageTurnor a custom announcement).blocksInteraction→ add.notEnabledtrait; setisAccessibilityElement = falseon interactive children.
Android / AccessibilityNodeInfo
Android exposes accessibility semantics via AccessibilityNodeInfo populated through ViewCompat.setAccessibilityDelegate() or Jetpack Compose Modifier.semantics {}.
role
Foundation role |
Android mapping |
|---|---|
button |
Button widget or setRoleDescription("button") |
checkbox |
CheckBox widget or setCheckable(true) |
combobox |
Spinner widget or setRoleDescription("combobox") |
dialog |
Dialog window; setDismissable(true) |
link |
setRoleDescription("link"); add ACTION_CLICK |
listbox |
RecyclerView with setCollectionInfo |
menu |
setRoleDescription("menu") on the container |
menuitem |
setRoleDescription("menu item") |
radio |
RadioButton widget or setCheckable(true) + setChecked |
slider |
SeekBar widget or RangeSemantics in Compose |
spinbutton |
setRoleDescription("spin button") |
switch |
Switch widget or setRoleDescription("switch") |
tab |
TabLayout tab item; setSelected(true) when active |
textbox |
EditText widget or TextField in Compose |
tooltip |
setTooltipText(text) |
tree |
setCollectionInfo with hierarchical structure |
progressbar |
ProgressBar widget or setRoleDescription("progress bar") |
meter |
setRoleDescription("meter"); setRangeInfo on AccessibilityNodeInfo |
grid |
setCollectionInfo with row/column counts |
listitem |
Children within a setCollectionInfo container |
group |
setRoleDescription("group") or CollectionInfo without selection |
intents
trigger→ setsetContentDescription()describing the action.select→ callsetSelected(true)on selection.expand/collapse→ callsetExpandable(true)andsetExpanded(true/false).choose→ addACTION_SCROLL_FORWARD/ACTION_SCROLL_BACKWARDactions.dismiss→ callsetDismissable(true); handleACTION_DISMISS.
focusable
true→setFocusable(true)andsetImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES).false→setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS)for decorative views. For composite views, manage focus viaperformAccessibilityAction(ACTION_ACCESSIBILITY_FOCUS, ...).
keyboardIntents
Relevant for physical keyboard input (Chromebook, external keyboard):
activate→ handleKeyEvent.KEYCODE_ENTER/KEYCODE_SPACE.navigate-options/navigate-items→ handle arrow keyKeyEvents.dismiss→ handleKeyEvent.KEYCODE_ESCAPEorKEYCODE_BACK.
wcag
Include criteria in component documentation and Accessibility Scanner audits. No automatic mapping.
State fields
announce→ callViewCompat.announceForAccessibility(view, announceText)or sendAccessibilityEvent.TYPE_ANNOUNCEMENT. In Compose, useLocalAccessibilityManager.current?.announce(...).communicates→ set the corresponding node property (e.g.,"expanded"→ callsetExpandable(true)thensetExpanded(true);"checked"→setChecked(true);"disabled"→setEnabled(false)).blocksInteraction→ callsetEnabled(false)on the root view. UseIMPORTANT_FOR_ACCESSIBILITY_YESto keep the element discoverable by AT as disabled; useIMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTSto hide the element and its subtree from AT entirely.
Voice and multimodal
Informative sketch. The following mapping is exploratory. Platform-specific voice adapter specs are deferred.
For voice interfaces (Alexa, Google Assistant, Siri Shortcuts) and future multimodal surfaces, the foundation vocabulary maps conceptually as follows:
| Foundation field | Voice / multimodal mapping |
|---|---|
role |
Utterance type in a voice schema (e.g., button → invocable command) |
intents |
Action category exposed in a voice intent catalog |
announce |
Spoken response on state transition |
communicates |
State value spoken when the user queries component status |
focusable |
Not applicable for purely voice surfaces |
keyboardIntents |
Not applicable for purely voice surfaces |
wcag
WCAG 2.x success criteria apply at the web layer. For voice and multimodal surfaces, WCAG is not directly applicable; accessibility requirements for these surfaces are deferred to platform-specific adapter specs.