React Native Accessibility: Creating Inclusive Apps in React Native — the coding bit


You may have come here looking for a list of recommended mobile accessibility resources, which I’ve compiled in a separate article.

This article is a companion to my talk at YOW! Connected Conference 2017 in Melbourne, Australia.

Abstract:

Creating inclusive and accessible apps is easy using React Native! Javascript developers can have a working app on their phone in less than an hour. Join me to see a working demo of how to plan, develop, and test for accessibility in the mobile space.
I will also be talking about resources and guidelines you can use and share with your organisation to develop apps for everyone — including what the bare minimum looks like to help you prioritise your requirements!

Continue below to learn how to code and debug accessible UI components in React Native.

Prior to reading this article, it is helpful if you have some experience in creating apps with React Native. If you are completely new to React Native, here’s a guide to get started — you’ll have a native app running on a simulator or your own device less than an hour! React Native currently only supports Android and iOS, with more platforms coming very soon…

React Native Accessibility

The most important and most useful resource is the React Native docs for Accessibility. There’s also a companion blog post about building accessibility into Facebook’s Ads Manager App.

From the React Native docs, there are many accessibility properties available to use with your JavaScript components, which will talk to the native accessibility APIs for both iOS and Android, providing an interface for which assistive technologies such as screen readers, braille displays, and switch controls can interact with your app. Here is the list of native accessibility attributes exposed in the React Native framework —

Used frequently:

  • accessible={true|false}
  • accessibilityLabel={'some label read by the screen reader'}

Sometimes used:

  • (iOS) accessibilityTraits={'none'|'button'|'link'|…}
  • (Android) accessibilityComponentType={'none'|'button'|'radiobutton_checked'|'radiobutton_unchecked'}

Have never used:

  • (iOS) accessibilityViewIsModal={true|false}
  • (iOS) onAccessibilityTap={someFunction()}
  • (iOS) onMagicTap={someFunction}
  • (Android) accessibilityLiveRegion={'none'|'polite'|'assertive'}
  • (Android) importantForAccessibility={'auto'|'yes'|'no'|'no-hide-descendants'}
  • AccessibilityInfo.fetch()

I found I’ve rarely needed to use anything except accessible and accessibilityLabel which I’ve highlighted in bold in the above list.

I’ve only used accessibilityTraits and accessibilityComponentType to describe button-like components that don’t use <Button>, e.g. <TouchableHighLight> or similar touchable components.

Next, let’s take a closer look at the most frequently used accessibility properties in React Native —

accessible={true} and accessibilityLabel

The accessible property is a little confusing to begin with.

When an element has the property accessible={true}, it’s actually makes the children of that element inaccessible. See the following example:

// The following code makes the button **inaccessible**
<View accessible={true}>   
<Text>Here is some text</Text>
<Text>And some more text</Text>
<Button
onPress={() => console.log('Button pressed!')}
title="Press me!"
accessibilityLabel="The button's accessibility label" />
</View>
// Screenreader transcript: 
// "Here is some text And some more text The button's accessibility label"

A few things to note here:

  1. The screen reader can only focus on the parent <View> component, and cannot access the children — therefore a screen reader user cannot navigate to the <Button> to tap on it
  2. The screen reader will read the component as a concatenation of its children’s accessibility labels or contents (if there’s no accessibilityLabel)
  3. The button’s accessibilityLabel has precedence over the button’s title. Without an accessibilityLabel, the screen reader will announce the button’s title instead

You will not often set accessible={true} on a <View>, and by default it is set to false. If you remove the accessible property altogether, the screen reader will be able to focus on each the children, and also activate the <Button>. Here’s how you make the above component completely accessible:

<View>   
<Text>Here is some text</Text>
<Text>And some more text</Text>
<Button
onPress={() => console.log('Button pressed!')}
title="Press me!" />
</View>
// Screenreader transcript: 
// "Here is some text" (user swipes right)
// "And some more text" (user swipes right)
// "Press me!, button" (user double taps to activate button)

accessibilityTraits and accessibilityComponentType

As much as possible, try to use semantic components when creating any user interface (that includes web too!). Semantic components reinforce the meaning of the component — i.e. what role is that component playing in the scheme of things?

Using a <Button> for a component that behaves like a button is always a good idea, because you get a lot of accessibility and usability properties for free. For example, the screen reader will announce “button” after reading the label of a <Button> component, and allow the user to activate it.

In real life, however, the primitive semantic components might not be enough to satisfy our requirements for a particular component. You might want to do some fancy styling, or add an icon, which isn’t possible with the primitive <Button> component — you might want to use a <TouchableHighlight> instead. For example:

<TouchableOpacity>
<View>
<Text> + </Text>
</View>
</TouchableOpacity>
// Screenreader transcript:
// "Plus"

Not particularly descriptive, right? This is a common mistake that is made for icon buttons, a very common pattern in mobile apps, and the screen reader simply doesn’t read anything out.

<TouchableOpacity 
accessible={true} // optional, this is the default
accessibilityLabel={'Add new thing'} // overrides child content
accessibilityTraits={'button'} // only works in ios
accessibilityComponentType={'button'} // only works in android
>
<View>
<Text> + </Text>
</View>
</TouchableOpacity>
// Screenreader transcript:
// "Add new thing, button"

If you are using the native-base library for your base UI components, be weary of the <Button> component! It actually uses <TouchableOpacity> in iOS, and <TouchableNativeFeedback> in Android. You can check the code out here. You’ll need to add both accessibilityTraits and accessibilityComponentType properties (someone should raise a pull-request to make those properties default to 'button'!)


For native app developers (and the curious) I’ve included the code snippets showing how native accessibility properties are mapped to React Native accessibility properties, below —

Bridge for accessibility properties in iOS — RCTViewManager.m
Native bridge for accessibility properties in Android — ReactViewManager.java
Native bridge for accessibility properties in Android — BaseViewManager.java

Debugging

I usually use OSX’s Accessibility Inspector to inspect my app in the iOS simulator, to check that the structure of my component is correct. After I finish a chunk of work, I then turn on VoiceOver and TalkBack to double-check everything is structured and labelled correctly.

Check my Mobile Accessibility Resources article, if you need a refresher on using Accessibility Inspector, VoiceOver, or TalkBack

Gotchas

In the many months that I’ve worked with React Native this year, I wanted to share a few gotchas that break accessibility in your app that took a while to catch, and caused me to pull a good chunk of my hair out.

1. All Touchable components are accessible={true} by default.

2. Some React Native libraries don’t expose (therefore, don’t pass through) accessibility properties in their UI components. I’ve had to fork a repo just to add one line to make an entire modal accessible again.

3. Things that work fine in iOS, often break horribly in Android. To create robust React Native apps that look good, get it working in Android first, and more than likely you will not have to change anything for iOS.

4. Upgrading libraries, especially React Native, can either fix everything, or break everything more. Always check a package’s Github Issues page first, if something strange is happening… like when a fresh React Native project crashes on launch.

Recently, a ‘stable’ release of React Native was horribly broken.
“React Native is the most extreme love & hate relationship I have.” — my favourite quote about React Native so far.

More coming soon… stay tuned!

Go forth and make amazing apps!