-
Notifications
You must be signed in to change notification settings - Fork 24.5k
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
Android: "accessible" prop issues. #30851
Android: "accessible" prop issues. #30851
Comments
This one likely needs investigation from the React Native team, as the "collapsible" prop has some complex behavior trying to automatically reduce the view hierarchy, which can cause accessibility problems if a view that has accessibility properties on it is collapsed, as well as the issues with accessible lying fairly deep in the view manager classes. Let me know if you need any help on determining what the proper behavior should be when this is used. |
Hey @blavalla! Thanks for reporting this. I think several of the issues you noticed are actually from the Views being completely removed because they have no "height" property. This is an optimization in React Native to avoid unnecessary renders. It's definitely worth clarifying this behavior in the documentation. I think this is also related to the issue where "invisible" accessibility Views are not able to be created (#30853) - accessible things need to be backed by a real component with current implementation. I've created a slightly updated snack to give the 3 previously unfocusable Views a small height. Those are now focusable. https://snack.expo.io/ksgL9sTHH Nested Accessible Text NodesI did notice the same issues as you with Text, where these are focusable as a single element:
I think what is happening here is that React Native is detecting the outer View as focusable. It then reads downward in the tree to put an "automatic" accessibility label which is all the Text within the View. The inner Text nodes are then completely ignored (even though they are marked focusable). What is the expected behavior here? Should it focus 3 times, first on the outer View and then once on each of the inner Text nodes? Or should it ignore the outer View and focus on the inner Text nodes individually? Nested Accessible ViewsSecond question - this is about the nested accessible Views:
Now that these are focusable (because I added height, I also added a background color to make it easier to see them when testing), the current behavior is to ignore the outer accessible View and instead focus twice - once on the View with label "Text One" and once on the View with label "Text Two". Is this expected? Or should it focus three times, or should it focus once and group both accessibility labels together (ex. by reading "Text One, Text Two")? |
@kacieb , both questions here are highlighting a core difference between iOS and Android. On iOS the default behavior (and really, the only reasonably possible behavior), would be that the outer view is focusable, and must be given an accessibilityLabel to describe it. No accessible element can have accessible descendants, and all accessible elements must have an accessibility label. Apple has has this very straightforward, and easy to understand for developers. Google on the other hand, has made things quite a bit more complicated. On Android, the default behavior in this situation would be that all three views (the outer View and the inner two Text views) would attempt to become focusable (assume that the accessible prop maps to Androids However, as with many things on Android related to accessibility, there are a few catches. Catch one, If instead the layout was like this:
The default Android behavior would actually be that the outer view and first text element would be focusable. Upon Talkback's focus evaluation of the outer view, it will automatically pull the "Text Two" text from the unfocusable child and use that text as the label. This will cause the confusing issue of the order of content being announced counterintuitively as "Text Two" (on focus of the parent) -> "Text One" (on focus of the first child). Looking at this layout, I think that very few people would expect this to be the behavior. Catch number two, if the layout was instead like this:
Then Talkback looks at this layout, and only sees the parent view as focusable. Knowing how it works from the previous example, you may expect it to announce "custom label, Text One, Text Two", since it has two unfocusable children now. But in this case, since an explicit label was provided, the unfocusable children are simply ignored. That text is never presented to the user. The assumption here is that by providing an explicit label, you are meant to describe that whole node, its children included, so the text of those children shouldn't be included. This is maybe expected if you come from an iOS background, but is also sort of a strange behavior. Finally, the last catch relates to why these views are considered focusable. We've been working with the assumption that they are only focusable because accessible="true", but this is not the only property that can make a view focusable on Android. Android also makes all elements with onClick listeners or onLongPress listeners focusable, as well as a bunch of other less-clear edge cases. If our layout looked like this:
Then all three elements would be focusable. The parent element due to accessible="true" and having an accessibilityLabel, and the child elements due to being clickable. The label on the parent does not force the children to become unfocusable if they are explicitly defined as being focusable via either the accessible prop or some other prop that causes implicit focusability like onClick. On iOS, this pattern would not be possible, as even children with click handlers that are descendants of an accessible element are not themselves focusable. The click handlers would instead have to be moved to the parent, or accessible="true" should not be set on it at all, and rather set on the individual child elements instead. I am probably a bit biased, but I think Androids behavior here is probably what users expect, and it's pretty strange that on iOS you can set a click listener on something that can't ever be clicked by some users simply due to one of its ancestors having the accessible prop (which you may not even realize is happening). This was a very roundabout way of saying that there is no real "one size fits all" approach to focusability between iOS and Android, and often even within Android itself. I think we need to decide what React Native developers expect to happen when using these properties, make sure that happens (if possible), and then document it well so that when things work differently from the default system it's explained why that is the case. |
@kacieb Another case that should be documented is when This pitfal is hitting consumers of |
TODOs 25th July (all completed)
|
accessible parent view and one not accessible child
iOS and Android have different behavior. <View accessible={true}>
<Text style={styles.paragraph} accessible={true}>
Text One
</Text>
<Text style={styles.paragraph}>
Text Two
</Text>
</View> video test on Android
Note: On android reproduces only with the changes from PR #33076. These are the classes responsible for handling text in ReactAndroid. 2022-07-26.16-02-46.mp4video test on iOS
VID_20220726_162112.mp4result on Android Expo
The difference in behavior is connected to the missing changes from PR #33076 in expo. Example from #30851 (comment). |
parent view with accessibilityLabel over-rides child Text
iOS and Android have the same behaviour. <View accessible={true} accessibilityLabel="custom label">
<Text style={styles.paragraph}>
Text One
</Text>
<Text style={styles.paragraph}>
Text Two
</Text>
</View> video test on Android
2022-07-26.16-28-43.mp4video test on iOS
VID_20220726_163034.mp4 |
parent view with accessibilityLabel does not over-ride children with onPress handler
On Android the expected behaviour was:
The actual behavior: Result: Fix tracked in #30851 (comment) <View accessible={true} accessibilityLabel="a couple of text views">
<Text style={styles.paragraph} onClick={some callback...}>
Text One
</Text>
<Text style={styles.paragraph} onClick={some callback...}>
Text Two
</Text>
</View> video test Android
2022-07-26.17-49-42.mp4video test iOS
VID_20220726_175107.mp4 |
screenreader difference in behavior between TalkBack and VoiceOver
#30851 (comment) #30851 (comment) #30851 (comment)
PR facebook/react-native-website#3226
|
view with no height and its children are not accessible with TalkBack
<View style={{height: 0}} accessible={true}>
<Text accessible={true}>text to be announced</Text>
</View> Tried the following solutions:
Related #27333 (comment)
|
accessible prop not working on some components
Kacie commented on the different behavior on Android between Nested Accessible Text Nodes and Nested Accessible Views #30851 (comment) The original issue with the Text component accessible prop was fixed with the changes from PR #33076.
|
Text onPress handler does not trigger setClickable(true) on Android
adding onPress handler to a Text Component does not call setClickable(true) (test case) PR #34284
|
>Finally, the last catch relates to why these views are considered focusable. We've been working with the assumption that they are only focusable because accessible="true", but this is not the only property that can make a view focusable on Android. Android also makes all elements with onClick listeners or onLongPress listeners focusable, as well as a bunch of other less-clear edge cases. If our layout looked like this: adding onPress handler to a Text Component does not call setClickable(true) ([test case](facebook#30851 (comment))) facebook#30851 (comment) verify if the accessible prop does not work on other components * Pressable, TouchableOpacity, Switch, TextInput, and TouchableNativeFeedback are focusable/accessible by default without an onPress handler * `<TouchableOpacity>` is accessible, `<TouchableOpacity accessible={false}>` is not accessible, `<TouchableOpacity accessible={false} onPress={() => console.log('pressed')}>` is accessible https://github.com/facebook/react-native/blob/a70354df12ef71aec08583cca4f1fed5fb77d874/Libraries/Components/Touchable/TouchableOpacity.js#L249-L251
>Sometimes this will group's descendants that are also marked as accessible together into one focusable element (e.g. accessible with accessible descendants), and sometimes it does not (e.g. accessible with accessible descendants). >It's definitely worth clarifying this behavior in the documentation facebook/react-native#30851 (comment) facebook/react-native#30851 (comment) facebook/react-native#30851 (comment) >On iOS the default behavior (and really, the only reasonably possible behavior), would be that the outer view is focusable, and must be given an accessibilityLabel to describe it. No accessible element can have accessible descendants, and all accessible elements must have an accessibility label. Apple has has this very straightforward, and easy to understand for developers. >Google on the other hand, has made things quite a bit more complicated. >On Android, the default behavior in this situation would be that all three views (the outer View and the inner two Text views) would attempt to become focusable (assume that the accessible prop maps to Androids focusable view property), but in this case the outer view would actually need some sort of label to be provided, as it has no content that it could announce. If no label was given, the outer view would become unfocusable and only the inner views would end up being focusable, which is exactly what you are seeing happen with the "Nested Text Views" example. If a label was given (and this label was mapped to the contentDescription property), then all three views would become focusable. - [x] [prepare documentation PR clarifying differences between iOS and Android](facebook/react-native#30851 (comment)) for scenarios [accessible parent view and one not accessible child](facebook/react-native#30851 (comment)) and [parent view with accessibilityLabel does not over-ride children with onPress handler](facebook/react-native#30851 (comment)) - [ ] iOS documentation for [Nested Text is not accessible](facebook/react-native#30851 (comment))
…ack (facebook#34284) Summary: >Finally, the last catch relates to why these views are considered focusable. We've been working with the assumption that they are only focusable because accessible="true", but this is not the only property that can make a view focusable on Android. Android also makes all elements with onClick listeners or onLongPress listeners focusable Adding onPress handler to a Text Component does not call setClickable(true) ([test case](facebook#30851 (comment))) facebook#30851 (comment) Pressable, TouchableOpacity, Switch, TextInput, and TouchableNativeFeedback are focusable/accessible by default without an onPress handler or accessible prop. ```jsx <TouchableOpacity /> ``` The TouchableOpacity is accessible ```jsx <TouchableOpacity accessible={false} /> ``` The TouchableOpacity is not accessible ```jsx <TouchableOpacity accessible={false} onPress={() => console.log('pressed')} /> ``` The TouchableOpacity is accessible. https://github.com/facebook/react-native/blob/a70354df12ef71aec08583cca4f1fed5fb77d874/Libraries/Components/Touchable/TouchableOpacity.js#L249-L251 This and other PRs fixes facebook#30851 ## Changelog [Android] [Fixed] - Text with onPress or onLongPress handler is not accessible with TalkBack Pull Request resolved: facebook#34284 Test Plan: main branch facebook#30 <details><summary>pr branch</summary> <p> <video src="https://user-images.githubusercontent.com/24992535/181207388-bbf8379b-71b8-44e9-b4b2-b5c44e9ac14d.mp4" width="1000" /> </p> </details> Reviewed By: cipolleschi Differential Revision: D39179107 Pulled By: blavalla fbshipit-source-id: 3301fb2b799f233660e3e08f6a87dad294ddbcd8
Description
On Android there are a number of odd irregularities with how accessible prop works.
accessible={true} and
accessibilityLabel="foo"` is not focusable.With this many inconsistencies with focus behavior, it's unclear what is working as intended by design, and what is working by accident, and what isn't working by design, and what isn't working by accident.
React Native version:
v0.63
Snack
https://snack.expo.io/jv1U2thqq
Expected Behavior
On Android accessible={true} should simply map to
view.setFocusable(true)
, andaccessible={false}
should map tosetImportantForAccessibility("NO")
. This will block elements with accessible="false" from ever being considered by the accessibility system, and force elements with accessibility="true" to be focusable by the system, even if they don't normally meet the requirements.Android Details
I think these issues all stem from the fact that "accessible" is handled in the ReactViewManager class, so it's set correctly on 's, but is not set in the BaseViewManager class, so doesn't do anything at all for other components like , , etc. This is counter to the fact that these components do allow you to set accessible="true" on them, so we should expect them to work correctly.
The text was updated successfully, but these errors were encountered: