Android: "accessible" prop issues.
See original GitHub issueDescription
On Android there are a number of odd irregularities with how accessible prop works.
- Sometimes this will group’s descendants that are also marked as accessible together into one focusable element (e.g. accessible <View> with accessible <Text> descendants), and sometimes it does not (e.g. accessible <View> with accessible <View> descendants).
- A <View> with
accessible={true} and
accessibilityLabel=“foo”` is not focusable. - Interaction between accessible prop and collapsable prop also causes strange behavior, while fixing the issues in some cases it causes incomplete labels in others.
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)
, and accessible={false}
should map to setImportantForAccessibility("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 <View>'s, but is not set in the BaseViewManager class, so doesn’t do anything at all for other components like <Text>, <Button>, 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.
Issue Analytics
- State:
- Created 3 years ago
- Reactions:1
- Comments:12 (3 by maintainers)
Top GitHub Comments
@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
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 thecontentDescription
property), then all three views would become focusable.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.
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 (https://github.com/facebook/react-native/issues/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 Nodes
I 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 Views
Second 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”)?