VoiceOver, TalkBack and reduced motion implemented hands-on
Accessibility in a React Native app isn't a CSS afterthought like on the web, it requires deliberately set accessibilityLabel values, roles and a well thought out focus order from the very start. Ignoring these fundamentals effectively excludes screen reader users of VoiceOver and TalkBack from the app, and since the European Accessibility Act it also carries legal consequences.
Table of contents
- 1. Why accessibility in React Native isn't a nice-to-have
- 2. The core accessibility props at a glance
- 3. Grouping elements and controlling focus order
- 4. Dynamic content and announcements
- 5. Respecting reduced motion
- 6. Touch targets and contrast in the design system
- 7. Manual testing with Accessibility Inspector and Scanner
- 8. Automated checks with an ESLint plugin
- 9. Platform differences and comparison
- 10. Summary
- 11. FAQ
1. Why accessibility in React Native isn't a nice-to-have
The European Accessibility Act (EAA) has, since June 2025, required a growing set of digital products in the EU, including many consumer apps with e-commerce or banking functionality, to demonstrate accessibility. For React Native teams, that means treating accessibility no longer as an optional polish step at the end of a project, but as a fixed part of every new component. If an app is made accessible only after the fact, the effort involved is significantly higher, because focus order and semantic structure are often deeply woven into the existing component architecture.
Beyond legal obligations, accessibility also pays off economically: millions of users rely permanently or situationally on screen readers, voice control, or enlarged text, and app stores themselves increasingly check basic accessibility criteria during review. An app that becomes unusable with VoiceOver enabled can, in the worst case, even be rejected for violating store policies, independent of any legal EAA obligation.
The decisive difference from web accessibility is that React Native has no DOM structure with HTML semantics. There is no automatic <button> element that communicates with the screen reader on its own, instead every interactive component has to explicitly declare its role, its state and its description through accessibility props. This exact lack of automation is what makes deliberate design for accessibility in React Native essential.
2. The core accessibility props at a glance
The foundation of any accessible component is the accessible={true} prop, which marks an element as a single, focusable unit for screen readers. Building on that, accessibilityLabel provides the spoken description read out instead of the visual text, while accessibilityHint additionally explains what happens on interaction, for instance "Opens the shopping cart menu". These two props should complement rather than repeat each other, a label like "Cart" with the hint "Opens the shopping cart" delivers more information than a redundant second sentence.
accessibilityRole tells the screen reader what kind of element it is, such as button, link, header or checkbox, which lets VoiceOver and TalkBack automatically offer matching hints and gestures. In addition, accessibilityState provides dynamic state information like disabled, selected or checked, and accessibilityValue is used for elements with a value range like sliders. For interactions beyond a simple tap, accessibilityActions combined with onAccessibilityAction enables additional actions triggerable via screen reader, such as swipe-to-delete.
// AccessibleCartButton.tsx - core accessibility props in practice
import { Pressable, Text } from 'react-native';
export function AccessibleCartButton({ itemCount, onPress, disabled }) {
return (
<Pressable
onPress={onPress}
disabled={disabled}
accessible={true}
accessibilityRole="button"
accessibilityLabel={`Shopping cart, ${itemCount} items`}
accessibilityHint="Opens the shopping cart overview"
accessibilityState={{ disabled }}
>
<Text>Cart ({itemCount})</Text>
</Pressable>
);
}
3. Grouping elements and controlling focus order
A product card with an image, title, price and rating stars shouldn't appear to screen reader users as four separate focus stops, but as one coherent unit. To achieve that, accessible={true} is set on the wrapping view, while the individual child elements themselves are no longer individually focusable, their text instead getting chained automatically into a combined description. Without this grouping, screen reader focus laboriously jumps through every single detail element, considerably slowing down navigation through long lists.
The order in which a screen reader navigates through the screen follows in React Native primarily the order in the component tree, not the visual arrangement via absolute positioning. With importantForAccessibility="no-hide-descendants", purely decorative containers can be removed entirely from the focus order, which is especially useful for icon overlays or background graphics that offer no semantic value to screen reader users.
A particularly error-prone case for incorrect focus order is absolutely positioned overlays like modals or toast messages, which visually float above the rest of the content but can be mounted elsewhere in the component tree. When a modal opens, screen reader focus should be explicitly moved to the first focusable element inside the modal, instead of staying on the background content, which shouldn't be interactive for the duration of the modal anyway. This behavior is often only noticed during an actual screen reader test, because it stays visually inconspicuous.
4. Dynamic content and announcements
When content changes without the user actively navigating, for instance after an asynchronous API call or during form validation, a screen reader user normally doesn't notice that change automatically. AccessibilityInfo.announceForAccessibility('message') triggers a spoken announcement independent of the current focus, making it the central tool for informing users about state changes such as "3 new messages received" or "Form submitted successfully".
Restraint in how often such announcements fire matters. If an announcement is triggered on every small UI change, it creates constant auditory noise that hinders navigation instead of aiding it. As a rule of thumb, announcements should be limited to events actually relevant to the user's task, such as errors, confirmations and larger context switches, not every minor visual update.
// FormSubmission.tsx - announce dynamic state changes explicitly
import { AccessibilityInfo } from 'react-native';
async function submitForm(formData) {
try {
await api.submit(formData);
AccessibilityInfo.announceForAccessibility('Form submitted successfully');
} catch (error) {
AccessibilityInfo.announceForAccessibility('Submission failed, please check the form fields');
}
}
5. Respecting reduced motion
Some users experience dizziness or discomfort from heavily animated transitions, a condition known as vestibular disorder. Both mobile operating systems offer a system-wide "reduce motion" setting that the app can query via AccessibilityInfo.isReduceMotionEnabled(). When this setting is active, elaborate parallax effects, large slide transitions and auto-playing video backgrounds should be replaced with simple fades or instant transitions.
Reanimated and other animation libraries usually provide their own hooks that reactively observe the system value, letting animation duration and complexity adapt at runtime without requiring an app restart. Adapting to the operating system's accessibility preferences is one of those cases where a single API call makes a noticeable difference for an entire user group without degrading the experience for anyone else.
6. Touch targets and contrast in the design system
The Apple Human Interface Guidelines require at least 44 by 44 points for every interactive element, while Google's Material Design guidelines mandate 48 by 48 dp. Smaller touch targets aren't only problematic for users with motor impairments, they also increase interaction error rates for all users, especially on the go or in unfavorable lighting conditions. In a design system, this minimum size should be defined as a global constant and enforced consistently in every pressable or touchable component.
Color contrast is the second major lever, especially in a design system with dark mode, where contrast ratios can differ substantially depending on the color scheme. The WCAG guideline of at least 4.5:1 for normal text and 3:1 for large text can be adopted from the web, but has to be validated separately for both color schemes, since a contrast sufficient in light mode can well be insufficient in dark mode.
Both requirements, minimum size and contrast, are most efficiently enforced through design tokens in the central theming system, instead of checking them manually in every individual component. A linting step that automatically checks color combinations from the theme against WCAG thresholds catches violations as soon as a new color variant is added, long before it ever shows up in production code.
7. Manual testing with Accessibility Inspector and Scanner
The Accessibility Inspector in Xcode shows, for every element on screen, the values actually passed to VoiceOver, including label, traits and hint, immediately revealing when an element is left without a meaningful label or is mistakenly recognized as a plain image instead of a button. On Android, the Accessibility Scanner plays a similar role and additionally flags automatically detectable issues like too-small touch targets or missing contrast directly on a screenshot of the app.
No tool, however, replaces actually testing with VoiceOver or TalkBack enabled while the app is running. Many issues, such as a confusing focus order or a missing announcement after an action, only become visible when actually navigating with eyes closed or the screen off. A brief manual walkthrough of the most important user flows with a screen reader enabled should be a fixed part of every larger feature review.
A practical compromise for teams without a dedicated accessibility specialist is a fixed checklist of the five or six most critical user flows, such as login, checkout and form submission, walked through with a screen reader enabled as a spot check on every larger release. That reliably catches the coarsest regressions without requiring the full effort of a comprehensive external accessibility audit on every release.
# iOS: enable VoiceOver via simulator accessibility shortcut
xcrun simctl spawn booted notifyutil -s com.apple.accessibility.voiceover 1
# Android: enable TalkBack via adb for automated test runs
adb shell settings put secure enabled_accessibility_services \
com.google.android.marvin.talkback/com.google.android.marvin.talkback.TalkBackService
8. Automated checks with an ESLint plugin
Since React Native has no direct equivalent to axe-core for automated DOM audits, a large part of automated checking shifts to static code analysis. eslint-plugin-react-native-a11y checks while code is being written whether interactive components have an accessibilityLabel, whether touchable components declare a matching role, and warns about common patterns like an image without an alternative for screen readers.
Additionally, Jest tests can verify whether critical components render the expected accessibility props at all, for instance through a snapshot test explicitly targeting the presence of accessibilityRole and accessibilityLabel. That doesn't replace a manual screen reader test, but it reliably prevents the regression where a previously accessible element loses its label through a later change.
{
"extends": ["plugin:react-native-a11y/all"],
"rules": {
"react-native-a11y/has-accessibility-hint": "warn",
"react-native-a11y/has-valid-accessibility-role": "error",
"react-native-a11y/touchable-has-accessibility-props": "error"
}
}
9. Platform differences and comparison
Although React Native provides a shared API for accessibility props, VoiceOver and TalkBack differ noticeably in detail behavior, gesture navigation and testing tools. A role name like accessibilityRole="header" is translated differently into native traits on iOS than the corresponding TalkBack role hint on Android, which can produce slightly different read-out behavior in edge cases.
For teams serious about investing in accessibility, testing on both platforms is always worthwhile, not just on the development team's primary target platform. The following overview summarizes the most important differences.
| Aspect | iOS VoiceOver | Android TalkBack | Shared RN API |
|---|---|---|---|
| Gesture navigation | Swipe left/right, double tap | Swipe left/right, explore by touch | accessibilityActions |
| Testing tool | Accessibility Inspector (Xcode) | Accessibility Scanner | No native RN alternative |
| Role translation | UIAccessibilityTraits | AccessibilityNodeInfo roles | accessibilityRole |
| Enabling for tests | notifyutil / Settings | adb settings put secure | No shared CLI command |
Mironsoft
React Native development, accessibility audits and EAA-compliant implementation
Want your React Native app accessible and EAA-compliant?
We run accessibility audits with VoiceOver and TalkBack, retrofit missing accessibilityLabel values and roles, and build reduced motion and contrast support directly into your design system.
Accessibility audit
Manual testing with VoiceOver and TalkBack across your most important user flows
Component retrofitting
Systematically adding accessibilityLabel, roles and focus order
ESLint integration
Wiring eslint-plugin-react-native-a11y into CI against regressions
10. Summary
Accessibility in React Native starts with the fundamental building blocks accessibilityLabel, accessibilityRole and accessibilityState, which every interactive component must explicitly declare because a native DOM semantic like on the web is missing. Grouping with accessible={true}, a well thought out focus order, and targeted announcements via AccessibilityInfo ensure that screen reader users can meaningfully navigate complex UIs instead of fighting through disconnected individual elements.
Reduced motion, sufficient touch targets and valid color contrast in both color schemes round out the technical implementation of accessibility. Manual testing with VoiceOver and TalkBack enabled remains essential, complemented by ESLint rules that prevent regressions during ongoing development. Given the European Accessibility Act, this investment is no longer optional but a legal and economic necessity for any serious React Native app.
React Native Accessibility — Key takeaways
Core props
accessibilityLabel, accessibilityRole and accessibilityState must be explicitly set on every interactive component.
Grouping & focus
accessible={true} on wrapper views bundles detail elements into one navigable unit.
Reduced motion
Query AccessibilityInfo.isReduceMotionEnabled() and simplify animations accordingly.
Testing
Combine Accessibility Inspector, Accessibility Scanner and manual VoiceOver/TalkBack walkthroughs.