Repository navigation
Set AccessibilityNodeInfo stateDescription for checked state on Android - #59009
BilalWalker wants to merge 2 commits into
Conversation
TalkBack speaks the unchecked state of a checkable node, and avoids announcing "checked" and "on" together for a switch, only when the node carries a stateDescription (API 30+). Native CompoundButton sets it, but ReactAccessibilityDelegate never did, so a React Native checkbox or radio with checked: false was announced with no state at all, and a switch with checked: true was read as "checked. on.". Set stateDescription whenever the checked state is a boolean: "on"/"off" for the switch role and "checked"/"not checked" otherwise, using the existing uimanager string resources. Fixes react#58739 Fixes react#58588
TalkBack already announces the checked state of a checkable node in the device language. React Native's state strings are translated by Meta's internal pipeline after import, so a new string would be English-only for a while. Keep the switch on/off strings, which are already translated in every locale, and only fill the case TalkBack is silent on: unchecked checkboxes and radios. Drop the unused state_checked_description.
|
Pushed cff022c in response to the discussion on #58739: for checkbox/radio, |
|
Thanks @BilalWalker, the narrowed version looks right to me. The checked state keeps TalkBack's own localised wording, the unchecked state finally gets announced, and switches reuse the already-translated on and off strings. For TalkBack users, an English "not checked" is a clear improvement on silence while the translations catch up. |
Summary:
On Android, TalkBack announces no state for an unchecked React Native checkbox or radio:
role="checkbox"/role="radio"witharia-checked={false}(oraccessibilityState={{checked: false}}) is read as just "Email me offers, tickbox", so a TalkBack user cannot tell an unchecked option from an element with no state at all (#58739). Arole="switch"withchecked: trueis read as "checked. on." (#58588).Both have the same root cause. Since API 30 TalkBack speaks the state of a checkable node from
AccessibilityNodeInfo.stateDescription, and only suppresses its own generic "checked" when one is set. NativeCompoundButton(CheckBox, RadioButton, Switch) always sets it;ReactAccessibilityDelegate.setStateonly setisCheckable/isCheckedand never calledsetStateDescription. No JavaScript prop reachesstateDescription, so apps cannot work around this.This change sets
stateDescriptionin the same placeisCheckable/isCheckedare set:switch:on/off, from the existingstate_on_description/state_off_descriptionresources, which are already translated in all 65 locales underuimanager/res.checkbox,radio,togglebutton, or no role) and unchecked:not checked, from a newstate_unchecked_descriptionresource.Only the case TalkBack is currently silent on gets a new string. Translations of the
uimanagerstrings come from Meta's internal translation pipeline ("Intl Scheduler" commits), not from pull requests, and the existinglink_description,rn_tab_descriptionandtimer_descriptionare in the same English-only state until it runs again. Keeping the checked state with TalkBack avoids a regression for non-English users in the meantime (see the discussion on #58739).The
mixedstring value keeps its existing handling inBaseViewManager.updateViewContentDescription.Fixes #58739
Fixes #58588
Changelog:
[ANDROID] [FIXED] - Set
stateDescriptionso TalkBack announces unchecked checkboxes and radios, and no longer reads "checked. on." for switchesTest Plan:
Result:
BUILD SUCCESSFUL,ReactAccessibilityDelegateTest: 22 tests, 0 failures (14 existing + 8 new).On Windows the Gradle run also needed
-Preact.internal.windowsBashPath=..., a pre-built codegen (yarn --cwd packages/react-native-codegen buildwith-x :packages:react-native:ReactAndroid:buildCodegenCLI) and a checkout path without spaces; none of that is part of this change.New Robolectric tests in
ReactAccessibilityDelegateTestcover: unchecked checkbox, checked checkbox (stateDescription stays unset), unchecked radio, unchecked state with no role, switch on and off viaaccessibilityRole, switch off via theroleprop, and that a state map withoutcheckedleavesstateDescriptionunset.I was not able to verify on a device with TalkBack; the expected announcements follow the behavior of native
CompoundButton, which TalkBack already handles this way.