question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Android: "accessible" prop issues.

See original GitHub issue

Description

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:closed
  • Created 3 years ago
  • Reactions:1
  • Comments:12 (3 by maintainers)

github_iconTop GitHub Comments

2reactions
blavallacommented, Mar 3, 2021

@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 the contentDescription 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:

     <View accessible={true}>
        <Text style={styles.paragraph} accessible={true}>
          Text One
        </Text>
        <Text style={styles.paragraph}>
          Text Two
        </Text>
     </View>

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:

     <View accessible={true} accessibilityLabel="custom label">
        <Text style={styles.paragraph}>
          Text One
        </Text>
        <Text style={styles.paragraph}>
          Text Two
        </Text>
     </View>

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:

     <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>

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.

1reaction
kaciebcommented, Mar 3, 2021

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:

     <View accessible={true}>
        <Text style={styles.paragraph} accessible={true}>
          Text One
        </Text>
        <Text style={styles.paragraph} accessible={true}>
          Text Two
        </Text>
     </View>

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:

    <View accessible={true}>
        <View
          style={styles.focusableViewRed}
          accessible={true}
          accessibilityLabel="Text One"
        />
        <View
          style={styles.focusableViewBlue}
          accessible={true}
          accessibilityLabel="Text Two"
        />
   </View>

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”)?

Read more comments on GitHub >

github_iconTop Results From Across the Web

Principles for improving app accessibility - Android Developers
To assist users with accessibility needs, the Android framework lets you create an accessibility service that can present content from apps ...
Read more >
Build accessible apps - Android Developers
Ensure your apps are as accessible as they can be. ... Report an issue related to accessibility development to Google.
Read more >
Make apps more accessible - Android Developers
Try to make your Android app usable for everyone, including people with accessibility needs. People with impaired vision, color blindness, ...
Read more >
Test your app's accessibility - Android Developers
We have integrated the Android Accessibility Test Framework in Android Studio to help you find accessibility issues in your layouts. To launch ...
Read more >
Make custom views more accessible - Android Developers
The default View implementation has a standard set of view properties, but if your custom view provides interactive control beyond a simple ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found