This blog post will explore and share some of the robust technical accessibility strategies and best practices we implemented for a mobile voting application using React Native.
Specifically, we’ll look at Formidable’s open-source library react-native-ama
which allowed us to construct a component library that served as accessible UI primitives. This approach let us focus on what we do best—building great applications—while simultaneously ensuring that we prioritize accessibility, baking it into each component by default. Then, it will mention a few other handy components and hooks we used, and hopefully save yourself a gotcha or two.
In many ways, building an accessible React Native mobile application is easier than ever before thanks to both React Native’s internal efforts as well as the community’s. With react-native-ama
it's not about adding accessibility features as an afterthought, but integrating them into the core process of your app development. As the react-native-ama
library puts it, it's about making "Accessibility a First-Class Citizen". (For more on this, check out this insightful blog post).
Component Libraries with react-native-ama
and Restyle
One of the best parts of react-native-ama
is that it provides a suite of component primitives with built-in best practices for accessibility. In essence, you get these accessibility features for free, just by using these components. Moreover, AMA has a range of runtime checks in development mode that automatically log violations and warnings, and even apply stylings to these violations, so it’s easy to see when and where you may have missed something.
Another standout feature of react-native-ama
is its "headless" architecture. This means you can use any component from the library while maintaining complete control over its styling and design. In our application, we used Shopify’s open-source restyle
theming library to handle the theming and styling of our components, all resulting in a standardized and accessible component library, fully equipped with dark mode, breakpoints, variants, and more. Together, this groundwork let us build features fast and efficiently.
Case Study: Touchable Elements
Let's consider a practical example. Our application has a variety of buttons and touchable elements, including large and small call-to-action buttons, multiple choice selection buttons, toggle buttons, external links, and more. To create these elements, we began with the Pressable
component from react-native-ama
, and then wrapped it in Restyle's createRestyleComponent
. This approach resulted in a completely themeable and accessibility-ready primitive component that we could then use to create more complex components.
Using this base button as a blueprint, we created our TouchableOpacity
and TouchableHighlight
components. These components were then wrapped to create call-to-action buttons, checkboxes, select boxes, and external links, as specified in the designs. This component abstraction allowed us to apply specific accessibility attributes that were relevant to each component. For example, we used accessibilityRole="checkbox"
and the accompanying state role checked={true/false}
for checkboxes, and accessibilityRole=“link”
for external links. By doing so, we ensured that we had components optimized for accessibility whenever we needed any sort of Pressable
inputs for any feature.
Rinse and Repeat
It's important to note that this strategy doesn't just apply to buttons and touchable elements. You can use this approach for any of the base components that AMA supports, including Text, TextInputs, Lists, and even the concept of Forms. Assembling a set of accessible primitives into your component library means that you can just build, knowing that you have at least a baseline of accessibility built in. You won't have to go back and "add it later.”
Escape Hatching & Other Related Examples
However, there may be cases where using components exported from react-native-ama does not work in certain situations, (perhaps you need to use components exported from react-native-gesture-handler, such as their Touchable).Fortunately, react-native-ama's documentation provides detailed guidlines explaining which accessibility features are included so that you can theoretically recreate these properties on a different base component. Additionally, you can always review the source code to see how it works. I think it’s important to reiterate that using react-native-ama doesn’t lock you in, you can use it as much or as little as is useful to you, and leverage it where needed.
Much like the button primitives we covered above, we also created a screen header component that included an autofocusing Text component. React-native-ama
's Text
autofocus
prop comes builtin into the component, and this component announces the new screen's title immediately during screen transitions when using VoiceOver/TalkBack. Without it, React-navigation tended to up end focussing on the back button when changing screens, so this was a nice touch of polish. Similarly, we implemented a React context that provided values on whether the user prefers reduced motion or not. This allows us to adjust animations and screen transitions accordingly.
When using a third-party bottom-sheet library, I found that it didn’t prevent users from navigating outside/beneath it’s content when in VoiceOver/TalkBack. To ensure that VoiceOver/TalkBack remained within the presented content and didn't navigate outside that content, we used a custom context and a Screen
component primitive that wrapped every screen. This made use of props such as accessibilityViewIsModal
, importantForAccessibility
, and accessibilityElementsHidden
when the bottom sheet was open so that VoiceOver/TalkBack properly stayed within the present content.
In these cases above, we were able to work with react-native-ama
and not against it to solve our specific use cases.
On Device Testing and Verification
It’s important to note that despite react-native-ama
offering excellent development runtime checks and a built-in a11y-inspector for macOS, it's crucial to test accessibility on real devices using VoiceOver on iOS or TalkBack on Android. Luckily, you can also learn all about that in this iOS VoiceOver Guide for Developers blog post! It’s important and worth it to learn and test any new features in a variety of circumstances. Much like how a feature isn’t complete in React Native until you’ve tested it with both iOS and Android, I’d argue a feature also isn’t complete unless you’ve tested it with dark mode, larger text settings, smaller text settings, and using VoiceOver/TalkBack!
Wrap up
While this blog post focused on one aspect of mobile application accessibility, the topic is wide and full of possibilities. I highly recommend checking out the official React Native accessibility documentation to learn more about what's possible in React Native out of the box. While design has not been discussed in this post, it plays an essential role in creating accessible applications; keep an eye out for a future blog post on this topic. And finally, even though every application will have different requirements, I hope this post demonstrates the solid foundation you can establish using react-native-ama
and the potential that lies in building from there.