Placeholder property included in TextInput Componenent prevents accessibilityLabel and accessibiltyHint from being read by screenreader
See original GitHub issueWhen including a placeholder property in a TextInput component, the accessibilityLabel and accessibilityHint properties are not read out in the screen reader. Example:
<TextInput accessible={true} accessibilityLabel={'My Accessibility Label'} accessibilityHint={'My Accessibility Hint')} accessibilityRole={'search'} style={[searchInput, { backgroundColor: searchFocus ? theme.INPUT_FOCUS_COLOR : theme.WHITE_COLOR }]} onFocus={(bool) => {set_searchFocus(bool);}} value={searchKey} inputAccessoryViewID={inputAccessoryViewID} placeholderTextColor={'#767676'} keyboardType= "default" returnKeyType={Platform.OS === 'ios' ? '' : "search"} enablesReturnKeyAutomatically={true} onSubmitEditing={() => {searchOrder();}} placeholder={formatMessageString('My Placeholder Text')} onChangeText={(searchString) =>{setSearchKey(searchString);}} underlineColorAndroid="transparent" />
One would expect the accessibilityLabel and Hint to be read out, and in my experience prior to 0.60 they were, along with the placeholder. Now, only the placeholder text his read out. Removing the placeholder property reads out the Label and Hint.
React Native version:
0.60.5
Steps To Reproduce
- Include a TextInput in project with accessibilityLabel, accessibilityHint, and placeholder text.
- Open project in device, turn on screenreader (e.g. TalkBack for Android, VoiceOver for iOS), and click the field.
- Click on field
- Note that screenreader only reads out the placeholder text, and not the accessibilityLabel and accessibilityHint.
- Remove placeholder text and reload.
- Click on field
- Note that accessbilityLabel and accessibilityHint are read out by the screenreader
Issue Analytics
- State:
- Created 4 years ago
- Reactions:8
- Comments:23 (2 by maintainers)
Top GitHub Comments
I’m putting my hat in the ring on this. We’ve spent the last couple days trying to figure out our field accessibility with Android. Digging into this it is clear that react-native’s accessibility on Android is broken, and I’ve got some evidence of our findings.
Essentially, we were able to discern that there are 4 variables that determine what Android or iOS says - existence of
value
,accessibilityLabel
,accessibilityHint
, orplaceholder
. React-Native on iOS seems to treat all of the scenarios logically, while Android does not. I’ve created a minimal example demonstrating all 7 combinations of specification ofaccessibilityLabel
/accessibilityHint
/placeholder
, and you can clear the text box/value to investigate the other 7 combinations (without value). As well, above each example I specify what Android/iOS reads with no value, and below each example I specify what Android/iOS reads with “1” as the value:This is available as a Snack. This is on react-native 0.61 from the Expo 37 SDK, but we’ve confirmed this in vanilla 0.62.2 in house as well. Furthermore, even if I switch the Snack version to Expo 34, the issue still applies for Android.
Essentially however, the bug is that react-native completely ignores the label if there is either a
value
orplaceholder
specified on the field, and if there is not aplaceholder
specified but avalue
exists, it only reads thevalue
.@jychiao, @tostringtheory , @HenrikDK2 and others.
Sorry for the delay on this, I work on Android accessibility, and understand the root cause of this issue, but only recently started working with React Native, so I didn’t know about this problem.
Unfortunately, the issue here is on Talkback’s side and there isn’t much that can be done in RN to resolve it directly, however we can work around the limitations that Talkback has. Let me explain how both iOS and Android work with text inputs, as they have some core differences that will make the problem a bit more clear.
On iOS with VoiceOver, when a text input is focused, VoiceOver will read the accessibilityLabel (if one exists), the accessibilityValue (if one exists and if not, the text input’s actual value), and the accessibilityHint (if one exists). The placeholder, being visible text inside the input is the “value” portion of that announcement. Just like when a user enters text, it visibly overrides the placeholder, any entered text will override the placeholder in the announcement as well. iOS’s behavior here is likely exactly what you expect when you see these three properties set on a text input.
Android on the other hand, is a bit different. On Android with Talkback, when a text input is focused, Talkback will read the text entered into the input (if any exists), then fallback to reading the placeholder if there us no entered text, and finally fall back to reading the contentDescription (Androids version of iOS’s accessibilityLabel) if no entered text or placeholder exists. Because of this fallback behavior, you will never be able to have a text input on Android with both an accessibilityLabel and content inside of it (either placeholder or user entered).
This logic in Talkback is shown in the compositor.json file here:
https://github.com/google/talkback/blob/master/compositor/src/main/res/raw/compositor.json#L1510-L1512
That line
$node.text
is referencing any entered text into the input, and the “fallback” block around it is saying to try to read that first, and only if it’s empty read the next line, which is$node.contentDescription
.So how does this impact accessibiltyHint you may ask? Well, accessibilityHint doesn’t actually exist on Android. This is an iOS-specific property that React Native has polyfilled into Android by simply concatenating this string into the contentDescription. Since the contentDescription is ignored in the fallback behavior above, so is the accessibilityHint.
@Akanksha-Thakur posted a workaround above, and the reason this works is that by setting a role of “none” rather than the default role of “edit_text” you are bypassing talkback’s edit-text-specific logic here. Unfortunately, that also means you are losing out on other edit-text-specific features, such as telling a user whether “editing mode” or “selection mode” is active.
Not all hope is lost however, as Android does have alternate ways to present this information in text inputs, but right now React Native doesn’t yet support them. These approaches have issues open here (#30846) and here (#31056) if you are interested in helping improve RN’s accessibility support.
In order to get something like iOS’s “accessibiltyLabel” to work on text inputs in Android, RN will need to support Android’s “labelFor” property. This is Android-specific and has no iOS counterpart. This property allows you to have one element act as a label for another, and in the example of text input, would allow an external element to label it even if it has content entered inside it. This is what that API could look like (psuedo-code), when built.
On focus, Talkback would announce “Brett, Edit Text for First Name”.
To fix the accessibilityHint issue, we’ll need to change how accessibilityHint works on Android, and rather than concatenating into the contentDescription instead add it elsewhere, such as the toolTipText property. TooltipText is another Android-specific feature, which is meant to contain text that is inside a tooltip pointing at an element. It can be used as a generic placeholder for secondary content about an element though, similar to iOS’s AccessibilityHint. If we changed accessibilityHint to instead set toolTipText on Android, and had the following code:
on focus Talkback would announce “Brett, Edit Text for First Name. Be sure not to enter any numbers or spaces.”
This would be very close to iOS’s behavior, missing only the 500ms pause before the accessibilityHint (which is already missing on Android).
I know this was a long comment, but I want to make sure the issue is well understood here, and that it’s clear that this isn’t a straightforward fix. If anyone wants to work on the two issues I mentioned in order to add support here, I’d be happy to review any and all PRs!