--- name: accessibility-mobile description: React Native accessibility patterns for iOS and Android. Use when implementing a11y features. --- # Accessibility Mobile Skill This skill covers accessibility (a11y) best practices for React Native apps. ## When to Use Use this skill when: - Building accessible components - Implementing screen reader support - Adding accessibility labels - Testing accessibility ## Core Principle **INCLUSIVE BY DEFAULT** - Accessibility is not optional. Build for all users. ## Basic Accessibility Props ```typescript import { TouchableOpacity, Text, View } from 'react-native'; // Accessible button Submit // Accessible image // Decorative image (hidden from screen readers) ``` ## Accessibility Roles ```typescript // Common roles ``` ## Accessible Forms ```typescript function AccessibleForm(): React.ReactElement { const [email, setEmail] = useState(''); const [emailError, setEmailError] = useState(''); return ( {/* Label association */} Email Address {emailError && ( {emailError} )} Submit ); } ``` ## Accessibility State ```typescript // Toggle state setIsChecked(!isChecked)} > {isChecked ? '☑' : '☐'} Accept terms // Expanded state setIsExpanded(!isExpanded)} > Show details // Selected state Tab 1 // Busy state ``` ## Accessibility Value ```typescript // Progress bar // Slider ``` ## Live Regions ```typescript // Announce changes to screen readers {statusMessage} // Toast/notification function Toast({ message, visible }: ToastProps): React.ReactElement | null { if (!visible) return null; return ( {message} ); } ``` ## Grouping Elements ```typescript // Group related elements iPhone 15 Pro $999 // Prevent grouping ``` ## Focus Management ```typescript import { useRef } from 'react'; import { AccessibilityInfo, findNodeHandle } from 'react-native'; function FocusExample(): React.ReactElement { const headerRef = useRef(null); const focusOnHeader = () => { const node = findNodeHandle(headerRef.current); if (node) { AccessibilityInfo.setAccessibilityFocus(node); } }; return ( Welcome Focus header ); } ``` ## Screen Reader Detection ```typescript import { useEffect, useState } from 'react'; import { AccessibilityInfo } from 'react-native'; function useScreenReader() { const [isEnabled, setIsEnabled] = useState(false); useEffect(() => { AccessibilityInfo.isScreenReaderEnabled().then(setIsEnabled); const subscription = AccessibilityInfo.addEventListener( 'screenReaderChanged', setIsEnabled ); return () => subscription.remove(); }, []); return isEnabled; } // Usage function MyComponent(): React.ReactElement { const isScreenReaderEnabled = useScreenReader(); return ( {isScreenReaderEnabled ? ( Detailed description for screen reader users ) : ( )} ); } ``` ## Reduce Motion ```typescript import { useEffect, useState } from 'react'; import { AccessibilityInfo } from 'react-native'; function useReduceMotion() { const [reduceMotion, setReduceMotion] = useState(false); useEffect(() => { AccessibilityInfo.isReduceMotionEnabled().then(setReduceMotion); const subscription = AccessibilityInfo.addEventListener( 'reduceMotionChanged', setReduceMotion ); return () => subscription.remove(); }, []); return reduceMotion; } // Usage with animations function AnimatedComponent(): React.ReactElement { const reduceMotion = useReduceMotion(); const animatedStyle = useAnimatedStyle(() => ({ transform: [ { scale: reduceMotion ? 1 : withSpring(scale.value), }, ], })); return ; } ``` ## Accessible Navigation ```typescript // Tab bar with proper accessibility function TabBar({ tabs, activeTab, onTabPress }) { return ( {tabs.map((tab, index) => ( onTabPress(index)} > {tab.label} ))} ); } ``` ## Accessible Lists ```typescript function AccessibleList({ items }) { return ( ( {item.title} )} accessibilityRole="list" /> ); } ``` ## Testing Accessibility ```typescript import { render, screen } from '@testing-library/react-native'; describe('Accessibility', () => { it('has correct accessibility role', () => { render(); expect(screen.getByRole('button')).toBeOnTheScreen(); }); it('has accessibility label', () => { render(); expect(screen.getByLabelText('Add to favorites')).toBeOnTheScreen(); }); it('announces state changes', () => { render(); expect(screen.getByRole('switch')).toHaveAccessibilityState({ checked: true, }); }); }); ``` ## Checklist - [ ] All interactive elements have `accessibilityRole` - [ ] All images have `accessibilityLabel` or are hidden - [ ] Form inputs have labels and error messages - [ ] Touch targets are at least 44x44 points - [ ] Color is not the only way to convey information - [ ] Text has sufficient contrast ratio (4.5:1) - [ ] Animations respect reduce motion setting - [ ] Focus order is logical - [ ] Dynamic content uses live regions - [ ] Screen reader testing on iOS and Android ## Notes - Test with VoiceOver (iOS) and TalkBack (Android) - Use Accessibility Inspector in Xcode - Enable accessibility testing in development - Consider users with motor impairments - Provide alternatives for gestures