Building animations in React Native with accessibility in mind
--
The Seer Mobile app helps people with epilepsy manage their seizures, track medications and have better conversations with their doctor.
Something that often gets overlooked when developing animations in apps is accessibility. I get it. You start by going down the rabbit hole of building complex animations (fun!), combining those animations with gestures (less fun), then trying to link that to react-navigation (“when will this end?”) and you’ve got yourself a pretty terrifying looking PR. Don’t get me started on unit tests.
In all that chaos of building complex animations, it’s easy to forget to implement accessibility handlers to ensure people with ‘Reduce Motion’ switched on receive a different experience. Reduce Motion doesn’t mean cut all animations. If you look at the way it’s implemented across the iOS and Android system stock apps, subtle animations like fades are all still maintained, but scale and transform animations get cut.
Why it’s important
Let’s get something straight, I love animations. If there was an option in settings called ‘Increase Motion’ I’d probably tick that but as much as animations bring joy and magic to user interfaces for many users — they can do the exact opposite for some. Around 3% of people with epilepsy can have seizures triggered by flashing lights and visual patterns, but users can opt to Reduce Motion for other reasons too.
Enabling on device
Thankfully, unlike voiceover, you can enable reduce motion from within iOS simulator and Android emulators.
On iOS
On iOS it’s pretty easy to enable reduce motion. Jump into the accessibility menu, tap ‘Motion’ and check ‘Reduce Motion’.
On Android, frustratingly the ‘Remove animations’ accessibility option in Settings doesn’t get picked up by React Native. I haven’t been able to find much documentation on how to access that using Android native code but I did find this gem in the React Native documentation.
So basically, on Android it’s a developer setting. And to access it these are the steps you (or a user) would need to take…
There’s an open issue talking about this over at https://github.com/facebook/react-native/issues/31221, if you know of a way to implement reacting to ‘Remove animations’ in native Java code I’d be happy to help patch React Native to get it in there.
Detecting ‘Reduce Motion’ using a hook
In React Native, ‘AccessibilityInfo’ has an event listener and async method for querying reduce motion which can be neatly bundled into a hook to use in animations.
You can also use https://github.com/infiniteluke/react-reduce-motion but for simple hooks like this I think it’s better to maintain the code yourself, especially if you’re using TypeScript.
Wiring it up to an animation
The easiest way to reduce the motion of an animation you’ve implemented is to find which styles have translates in them and swap them out for opacity transitions. It’s neat and can hook in with gestures pretty easily but it comes with the limitation of needing to use a constant animation duration across all your animated styles.
Here’s some example code that implements the hook we created above.
Here’s what it looks like side by side in the simulator.
Support in open source libraries
Reduce Motion support in popular React Native libraries like react-navigation is spotty at the moment so while you’re implementing it in your own projects, there’s an awesome opportunity to get some high-profile open-source PR’s in and make React Native more accessible in the process.
You can check out the useReduceMotion hook at https://gist.github.com/martsie/4f3bf6e3c67cf032ba562745a9c161af and example implementation at https://gist.github.com/martsie/a09b8192fe9ad40afc1a26ffc1c3c81c.
If you’re interested in working on delightful, accessible apps built in modern tech that genuinely improves the lives of people with epilepsy, come work with us at Seer!