Description
On Android, a checkbox or radio built with role="checkbox" or role="radio" and aria-checked={false} is announced by TalkBack with its name and role but no state. For example: "Email me offers, tickbox". Once checked, TalkBack announces "Ticked" (en-GB wording for "checked"). A TalkBack user cannot tell an unchecked option from an element that has no state at all. The legacy accessibilityState={{ checked: false }} behaves the same.
The unchecked state does reach Android. A pressable radio with aria-checked={false} gets TalkBack's "double-tap to toggle" hint, which TalkBack only gives checkable nodes. The same radio without aria-checked gets "double-tap to activate". So ReactAccessibilityDelegate.setState is setting isCheckable = true and isChecked = false as intended.
What is missing is AccessibilityNodeInfo.stateDescription. Native CompoundButton (CheckBox, RadioButton, Switch) sets it on API 30 and later, and current TalkBack speaks the unchecked state from it. In the same screen, a native Switch that is off is announced as "off". ReactAccessibilityDelegate never calls setStateDescription, so TalkBack has nothing to speak for the unchecked case. I checked the source of 0.81.0, 0.86.3, 0.87.1 and main at commit d194d8b (29 September 2026). None of them set stateDescription.
TalkBack output on the device, one element per line. This was recorded on 0.86.3 in an app with the same props as the Snack. Element names are changed to match the Snack.
- Native
Switch, off: "Native switch, off". This is the control and works.
role="checkbox", aria-checked={false}: "Email me offers, tickbox". No state.
role="radio", aria-checked={false}: "Express, radio button". No state.
accessibilityRole="radio", accessibilityState={{ checked: false }}: "Express, radio button". No state.
role="radio" checked, then unchecked by double-tapping: "Ticked, Express, radio button", then "Express, radio button".
role="radio" with no aria-checked: "Express, radio button". Identical to the unchecked radio.
role="radio", aria-checked={false}, with onPress: "Probe J, radio button, double-tap to toggle".
role="radio", no state, with onPress: "Probe K, radio button, double-tap to activate".
Expected: unchecked checkboxes and radios announce "not ticked" or "not checked", as native CheckBox and RadioButton do.
Related:
App code cannot work around this. No JavaScript prop reaches stateDescription, and accessibilityValue.text only appends to contentDescription, which changes the accessible name.
Suggested fix: in ReactAccessibilityDelegate.setState, when checked is a boolean, also set info.stateDescription to a localised "checked" or "not checked" string. For role="switch" use "on" or "off", as #58588 proposes.
Steps to reproduce
- Open the Snack below on an Android device with TalkBack on.
- Swipe to the native switch. TalkBack says "Native switch, off".
- Swipe to "Email me offers". TalkBack says "Email me offers, tickbox", with no state.
- Swipe to the "Express" radio. TalkBack says "Express, radio button", with no state.
- Double-tap "Express", then swipe away and back. TalkBack now says "Ticked, Express, radio button", and "Standard" has no state.
- Swipe to Probe J and Probe K. J ends "double-tap to toggle" and K ends "double-tap to activate". This shows the unchecked state reached the node without being spoken.
React Native Version
0.86.3
Affected Platforms
Runtime - Android
Output of npx @react-native-community/cli info
System:
OS: Windows 11 10.0.26200
CPU: (14) x64 Intel(R) Core(TM) Ultra 7 155U
Memory: 17.80 GB / 31.52 GB
Binaries:
Node: 24.15.0
npm: 11.12.1
npmPackages:
react: 19.2.3
react-native: 0.86.3
Run in Expo Go on Expo SDK 57, which uses the New Architecture.
Device: Samsung Galaxy S25 Ultra, Android 16, TalkBack 17.
Stacktrace or Logs
No crash or error. This is an accessibility output bug. The TalkBack output for each element is under Description.
MANDATORY Reproducer
https://snack.expo.dev/@saleeem4rahman/talkback-unchecked-state-missing
Snack saved this on Expo SDK 54 (React Native 0.81). The TalkBack output above was recorded on 0.86.3 with the same props, and the Android accessibility delegate in 0.81 also sets setCheckable(true) without a state description.
Screenshots and Videos
None. The behaviour is audible, not visible.
Description
On Android, a checkbox or radio built with
role="checkbox"orrole="radio"andaria-checked={false}is announced by TalkBack with its name and role but no state. For example: "Email me offers, tickbox". Once checked, TalkBack announces "Ticked" (en-GB wording for "checked"). A TalkBack user cannot tell an unchecked option from an element that has no state at all. The legacyaccessibilityState={{ checked: false }}behaves the same.The unchecked state does reach Android. A pressable radio with
aria-checked={false}gets TalkBack's "double-tap to toggle" hint, which TalkBack only gives checkable nodes. The same radio withoutaria-checkedgets "double-tap to activate". SoReactAccessibilityDelegate.setStateis settingisCheckable = trueandisChecked = falseas intended.What is missing is
AccessibilityNodeInfo.stateDescription. NativeCompoundButton(CheckBox, RadioButton, Switch) sets it on API 30 and later, and current TalkBack speaks the unchecked state from it. In the same screen, a nativeSwitchthat is off is announced as "off".ReactAccessibilityDelegatenever callssetStateDescription, so TalkBack has nothing to speak for the unchecked case. I checked the source of 0.81.0, 0.86.3, 0.87.1 andmainat commit d194d8b (29 September 2026). None of them setstateDescription.TalkBack output on the device, one element per line. This was recorded on 0.86.3 in an app with the same props as the Snack. Element names are changed to match the Snack.
Switch, off: "Native switch, off". This is the control and works.role="checkbox",aria-checked={false}: "Email me offers, tickbox". No state.role="radio",aria-checked={false}: "Express, radio button". No state.accessibilityRole="radio",accessibilityState={{ checked: false }}: "Express, radio button". No state.role="radio"checked, then unchecked by double-tapping: "Ticked, Express, radio button", then "Express, radio button".role="radio"with noaria-checked: "Express, radio button". Identical to the unchecked radio.role="radio",aria-checked={false}, withonPress: "Probe J, radio button, double-tap to toggle".role="radio", no state, withonPress: "Probe K, radio button, double-tap to activate".Expected: unchecked checkboxes and radios announce "not ticked" or "not checked", as native CheckBox and RadioButton do.
Related:
stateDescription, TalkBack reads "checked. on.". SettingstateDescriptionwhenevercheckedis set would fix both.stateDescription.App code cannot work around this. No JavaScript prop reaches
stateDescription, andaccessibilityValue.textonly appends tocontentDescription, which changes the accessible name.Suggested fix: in
ReactAccessibilityDelegate.setState, whencheckedis a boolean, also setinfo.stateDescriptionto a localised "checked" or "not checked" string. Forrole="switch"use "on" or "off", as #58588 proposes.Steps to reproduce
React Native Version
0.86.3
Affected Platforms
Runtime - Android
Output of
npx @react-native-community/cli infoRun in Expo Go on Expo SDK 57, which uses the New Architecture.
Device: Samsung Galaxy S25 Ultra, Android 16, TalkBack 17.
Stacktrace or Logs
MANDATORY Reproducer
https://snack.expo.dev/@saleeem4rahman/talkback-unchecked-state-missing
Snack saved this on Expo SDK 54 (React Native 0.81). The TalkBack output above was recorded on 0.86.3 with the same props, and the Android accessibility delegate in 0.81 also sets
setCheckable(true)without a state description.Screenshots and Videos
None. The behaviour is audible, not visible.