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.

Text Component doesn't disable click functionality when disabled

See original GitHub issue

Description

When a Text component passes true to disabled prop, the component does not announce “disabled” when using a screen reader.

import {Text, View, StyleSheet} from 'react-native';
import * as React from 'react';

type Props = SurfaceProps<PlaygroundRoute>;

export default function Playground(props: Props): React.Node {
  const [pressCount, setPressCount] = React.useState(0);
  return (
    <View style={styles.container}>
      <Text> Press count: {pressCount} </Text>
      <Text
        style={styles.text}
        accessibilityState={{disabled: true}}
        accessibilityRole="button"
        onPress={() => setPressCount(pressCount + 1)}>
        Case 1: onPressText - disabled via accessibilityState prop
      </Text>
      <Text
        style={styles.text}
        disabled
        accessibilityRole="button"
        onPress={() => setPressCount(pressCount + 1)}>
        Case 2: onPressText - disabled via disabled prop
      </Text>
      <Text
        style={styles.text}
        accessibilityRole="button"
        onPress={() => setPressCount(pressCount + 1)}>
        Case 3: onPressText - not disabled
      </Text>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    padding: 10,
    paddingTop: 30,
  },
  text: {
    margin: 20,
  },

The above examples have the following problems (bolded text highlights problems)

Case Android (with TalkBack off) Android (with TalkBack on) iOS (VoiceOver off) iOS (VoiceOver on)
Case 1: disabled from accessibilityState Can still trigger onPress Can still trigger onPress although it reads the button as disabled Can still trigger onPress Can still trigger onPress – does not read button as disabled
Case 2: disabled from disabled prop Cannot trigger onPress Cannot trigger onPress Can still trigger onPress Can still trigger onPress – does not read button as disabled
Case 3: not disabled Can trigger onPress Can trigger onPress Can trigger onPress Can trigger onPress

Notes:

  • You must set accessibilityRole=“button” to correctly tell VoiceOver/TalkBack that the Text component is clickable and screen reader will relay that information.

Identified Problems:

  • Android’s disabled via accessibilityState allows you to still trigger onPress behavior regardless if you have TalkBack on.
  • iOS also disregards accessibilityState’s disabled regardless of VoiceOver – onPress will be triggered.
  • iOS disregards disabled prop – onPress will be triggered regardless of VoiceOver
  • iOS VoiceOver does not read buttons as disabled regardless via accessibilityState or disabled prop.

Reproduce:

  • On Android device, toggle TalkBack (Settings > Accessibility > TalkBack on (turn on volume shortcut for ease)
  • On iOS device, toggle VoiceOver by (Settings > Accessibility > VoiceOver). You can also turn on a shortcut (Settings > Accessibility > Accessibility Shortcut > VoiceOver) which allows you to triple click the power button to turn off/on VoiceOver.
  • Render the above example
  • Swipe to focus on the 3 different cases to test

React Native version:

0.63

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Comments:6 (5 by maintainers)

github_iconTop GitHub Comments

5reactions
blavallacommented, Feb 17, 2021

Edit: I’ve since realized that we need to set accessibilityRole=“button” for the screenreaders to recognize something as clickable. Have also identified that this is an issue on iOS – updated the description.

This is not expected, as setting the onPress prop should ideally be enough to trigger this to be recognized as clickable. If that’s not the case, that’s probably a separate issue that should be fixed. On iOS, you may need to also set accessible={true}, but I am assuming that was the case already, as otherwise it would probably not have even been focusable.

Follow up question: Should we make the accessibilityRole default as “button” when an onPress or onLongPress is defined?

This is a very common pattern on iOS, where almost all clickable elements have a trait of “button”, but it is not very common on Android, where “button” is really only used for things that look and act like buttons. Because of this, I don’t think we should make this change globally.

I notice that we hardcode the values of selected and enabled here and it seems to be expecting that somewhere we override these values by the comment immediate following: But setting a breakpoint here where we read the accessibilityState to initializeAccessibiltyNodeInfo, it never gets hit in the examples above.

So it seems like we intended for the view properties of enabled/selected to be hard coded (I’m not sure why we would, but thats pretty clear from the code), and instead only represent these on the AccessibilityNodeInfo.

To give some context, for every View, an AccessibilityNodeInfo is created and initialized in the onInitializeAccessibilityNodeInfo method. This method is meant to sync the current state of the view properties to the same properties on the AccessibilityNodeInfo. This method is called basically every time a view property changes, so the AccessibilityNodeInfo should always be up to date with the state of the View. Talkback parses the contents of these AccessibilityNodeInfos to inform its announcements and behavior.

The way we’re hard-coding the view properties will cause issues however, since while Talkback really only parses the AccessibilityNodeInfo for it’s information, when it does that parsing is often triggered off of events that Android’s View system fires, and these events are often fired based on view property changes. If the view properties are not changing, these events will not get fired, so Talkback’s will not react to them. There is really no good reason to intentionally have the View and the AccessibilityNodeInfo out of sync like we have them.

As a bonus, if we keep these in sync, we likely only need to set them on the View, as the super() call in the onInitializeAccessibilityNodeInfo’s method will take care of syncing them to the AccessibilityNodeInfo for us.

1reaction
lunaleapscommented, Feb 16, 2021

I notice that we hardcode the values of selected and enabled here and it seems to be expecting that somewhere we override these values by the comment immediate following:

// For states which don’t have corresponding methods in // AccessibilityNodeInfo, update the view’s content description // here

But setting a breakpoint here where we read the accessibilityState to initializeAccessibiltyNodeInfo, it never gets hit in the examples above. I’m unfamiliar with how accessibiltyNodeInfo should be used but I see it being referenced in something like ReactHorizontalScrollView – does this imply that Text needs to do something as well? cc @blavalla if you have more context on how AccessibilityNodeInfo is used typically in Android views?

Read more comments on GitHub >

github_iconTop Results From Across the Web

Clicking a disabled input or button - javascript - Stack Overflow
There is no way to capture a click on disabled elements. Your best bet is to react to a specific class on the...
Read more >
How to disable or enable buttons using Javascript and jQuery
Learn how to enable or disable buttons using javascript and jQuery based on whether the input field is filled or empty.
Read more >
Accessibility and usability considerations for disabling buttons ...
HTML buttons and inputs accept a Boolean disabled attribute. This stops the element from receiving events like clicks and keyboard focus.
Read more >
How to disable a button in React | bobbyhadz
Use the disabled prop to disable a button in React, e.g. <button disabled={true}>Click</button> . You can use the prop to conditionally disable the...
Read more >
HTML attribute: disabled - HTML: HyperText Markup Language
The Boolean disabled attribute, when present, makes the element not mutable, focusable, or even submitted with the form.
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