The perfect app is an app that is accessible to everyone. This means that every functionality, every option and every new feature should be designed the way that it is accessible regardless of the user’s disability level.

For the past few weeks, I and my team have worked on adjusting and fixing a couple of accessibility issues in one application. The level of complexity of the problems varied. Sometimes the solutions that we implemented required quite a while of our work. I’ve decided to collect a few and briefly describe them to make other developers aware of potential problems that they might face.

React Native Accessibility API

Finally, React Native’s Accessibility API is awesome 🎉! The version 0.60.0 comes with updated AccessibilityInfo along with many improvements to Accessibility such as additional accessibility roles, states and accessibility actions. Check the React Native blog post for more info.

One new method called isScreenReaderEnabled() can be especially helpful to distinguish whether the screen reader is currently active to rearrange the screen or disable some animations.

Accessibility Tools

Working on accessibility without a physical device is not an issue. However, in contrast to iOS, Android emulator doesn’t have the screen reader app installed by default. To install it, you can download Android Accessibility Suite from Google Play or download TalkBack file and drag the downloaded .apk on a simulator according to React Native documentation.

Android Accessibility Suite is a collection of accessibility services that help you use your Android device eyes-free or with a switch device.

Another helpful app available on the store is AccessibilityScanner. It scans the current screen and suggests some hints to improve the accessibility level of your app.

I’m sure my app is fully accessible…

Are you sure about that? I am aware it’s quite hard to test the app on all available Android devices, however, I would advise you to pay special attention to enabling the largest font size along with the largest display size and test the app one more time on devices with an 18:9 (or 18,5:9) aspect ratio. Popular representatives of these proportions can be Xiaomi Redmi 5 Plus or Samsung Galaxy S8. 

Wait, but why is it so important? The answer is simple! Items that fit on the default screen and font sizes can be truncated or remain off-screen because mentioned devices provide a taller screen with slimmer bezels on the sides. There is no reason to worry because in most cases it can be easily fixed by setting flexWrap or flexShrink properties to get the right layout.

Need help with React Native?

Hire us

So what are my tips?

Well, I think it’s about time to dive into examples. Check out the first one!

1. Inline Text Link

Let’s start with the problem when a link that opens the provided url is located between two parts of a sentence. An example is pretty simple, look at it:

Check my activity on the Callstack's webpage

We expect that first, the screen reader reads the whole sentence and then it tells what accessibility role it plays. In this case, it is a link so on double-tap (double-tap gesture is used to activate the selected item when TalkBack is on) it will open a browser installed on the device.

To achieve it, there is a need to wrap everything in one parent Text component with an accessibilityRole=”link” and onPress function dependent on the method isScreenReaderEnabled from AccessibilityInfo.

It should look like:

I’m sure that you’ve also come across cases where the sentence contains two inline links e.g.

Check my activity on the Callstack's webpage or github.

My advice at this point is to again take advantage of isScreenReaderEnabled method to rearrange the view and split the phrase into two.

2. Views differences

We should keep in mind that each time the font and the display sizes are increased, the screen content will also visually enlarge. Consequently, it may not fit on one screen height, therefore, potentially large content should be placed within ScrollView rather than View where the layout is static, not scrollable.

3. Icon size

Icons are usually presented along with a label to clarify action or attract attention. Once the font or display size is increased SVG icons or icons build on the top of the Text components should be scaled as well. That’s the case when PixelRatio module comes in handy. More precisely, getFontScale() method is going to be the multiplier to the initial font size.

NOTE: PixelRatio.getFontScale() is specific to the Android platform (on iOS it will always return the default pixel ratio).

4. Text Input Placeholder

The placeholder should specify a short hint that describes what is the expected value of an input field. An issue arises if the placeholder message isn’t short and on the largest font size it remains off the screen. A good practice is to first discuss the problem with the designer and try to rephrase the message or redesign screen a little.

However, if you cannot change anything, I propose to find a fitting font size, based on recursive calculations and accurate text measuring before laying it out. You can do that using react-native-text-size library.

In this approach, the goal is to calculate a font size at which the placeholder message is displayed correctly. To achieve it, I’ve created a method called calcFontSize which accepts three arguments such as:

  • fontSize — an initial font-size value, that is going to be decreased,
  • containerWidth — text input container width, that has to fit a placeholder,
  • text — placeholder message.

Within the function, the font size will be constantly reduced by one until a placeholder width is smaller than the width of the container.

That method is called within the onLayout prop

in TextInput component

Pretty easy, isn’t?  The font size will be decreased a bit, but the whole message will be displayed and the user will not be confused.

NOTE: If you are afraid of the flash during placeholder changes you can add one more field to the state called e.g. measured that will be set to true after calculation and make placholderTextColor conditional to the measured value, like in the example below:

5. Navigation Header

The logic presented in point 4 can be fully adopted also in the case when the header title is too long and your requirements are saying that it cannot be wrapped into two lines. Hiding some part of the title can lead to misunderstanding, so my recommendation is to shrink the font size and display it completely. To accomplish that reuse the previously created function calcFontSize within onLayout prop in custom-made navigation header based on Text component, which can simply look like:

and then use it inside navigationOptions

6. Styling in WebView

A page displayed inside WebView is an integral part of your app. Keep in mind that some websites may not be adjusted to be accessible. Generally, it’s not your fault, but in my opinion, if there is a possibility to fix it from point of view of your code, you should do it.

In that case, a prop called injectedJavaScript can be helpful. It allows you to inject some JavaScript code into the web page when the view loads.

To easily find an element that you need to adjust, check the Google guide how to easily debug WebView pages on Android using Chrome Developer Tools or react-native-webview documentation.

To illustrate the problem, take a look at the header on Linkedin webpage that on the largest font size remains slightly off the screen, instead of being broken to prevent overflow. That case can be fixed by adding word-break style property into h1 tag.

Some styles that you want to change can have !important rule applied, but no worries, you can overwrite it! The CSSStyleDeclaration.setProperty() method comes in handy. It is used for setting a new value for a property on a CSS style declaration object.

Let’s assume that you have to correct the width of the image to get width: 100%. What you need to do is to set a proper value along with the priority and then inject it into the webpage, like in the example:

7. Abbreviations and Acronyms

Do you think that the screen reader is able to distinguish between an abbreviation or an acronym and a regular word that are written the same way? Take, for example, ICE which stands for In Case of Emergency, but means frozen water at the same time. Screen reader, unfortunately, doesn’t care about the capitalization, so in both cases, we are going to hear /ʌɪs/. What we can do about that is to interject whitespace between characters. How?

To achieve that firstly we need to use regex to find an acronym, then use a split() method on it and finally, join substrings with the whitespace character.

Now all that remains to do is to use replace() function within accessibilityLabel :

8. Acronym in AppName

Did you wonder what if your app name contains acronym? The app name is located in a file strings.xml so, there’s some wiggle room. The solution is even simpler than manipulations on a string from the previous example, but the approach is the same — characters have to be separated, only for the screen reader. There can’t be any visual effect so a must-have in that case is Unicode Character ‘ZERO WIDTH NO-BREAK SPACE’ — \uFEFF .

Well, sometimes one space is not enough, especially if your app name starts with a vowel and … you are using Samsung devices. It’s not a joke, TalkBack on Samsung devices acts differently than on other Android devices and has a serious problem with reading app names such as my example name AAT (Accessibility Android Tips).

Wrong pronunciation

In such circumstances, you have two double number of no-break space characters. Look at the final app name result:

and check the correct pronunciation

Correct pronunciation

Conclusion

Everyone wants to deliver a 5-star-rated UX application, but remember that your app is perfect when 100% of users can use and enjoy it. Don’t forget about people with disabilities, because all the users deserve the same level of availability. I hope I drew your attention to the accessibility problem and some of the tips that I shared in this article come in helpful and will make your work easier.


I would like to say a special thank you to Weronika Czekalska, Kacper Wiszczuk and Jakub Kłobus.

No Comments