React Native accessibility patterns for iOS and Android. Use when implementing a11y features.
/plugin marketplace add IvanTorresEdge/molcajete.ai/plugin install ivantorresedge-react-native-tech-stacks-js-react-native@IvanTorresEdge/molcajete.aiThis skill inherits all available tools. When active, it can use any tool Claude has access to.
This skill covers accessibility (a11y) best practices for React Native apps.
Use this skill when:
INCLUSIVE BY DEFAULT - Accessibility is not optional. Build for all users.
import { TouchableOpacity, Text, View } from 'react-native';
// Accessible button
<TouchableOpacity
accessible={true}
accessibilityRole="button"
accessibilityLabel="Submit form"
accessibilityHint="Double tap to submit your information"
onPress={handleSubmit}
>
<Text>Submit</Text>
</TouchableOpacity>
// Accessible image
<Image
source={require('./profile.png')}
accessible={true}
accessibilityLabel="Profile picture of John Doe"
/>
// Decorative image (hidden from screen readers)
<Image
source={require('./decoration.png')}
accessible={false}
accessibilityElementsHidden={true}
importantForAccessibility="no-hide-descendants"
/>
// Common roles
<TouchableOpacity accessibilityRole="button">
<TouchableOpacity accessibilityRole="link">
<TextInput accessibilityRole="search">
<Switch accessibilityRole="switch">
<Image accessibilityRole="image">
<Text accessibilityRole="header">
<Text accessibilityRole="text">
<View accessibilityRole="alert">
<View accessibilityRole="checkbox">
<View accessibilityRole="radio">
<View accessibilityRole="tab">
<View accessibilityRole="tablist">
<View accessibilityRole="progressbar">
<View accessibilityRole="slider">
function AccessibleForm(): React.ReactElement {
const [email, setEmail] = useState('');
const [emailError, setEmailError] = useState('');
return (
<View>
{/* Label association */}
<Text nativeID="emailLabel">Email Address</Text>
<TextInput
value={email}
onChangeText={setEmail}
accessibilityLabel="Email Address"
accessibilityLabelledBy="emailLabel"
accessibilityRole="none"
keyboardType="email-address"
autoComplete="email"
textContentType="emailAddress"
// Error state
accessibilityInvalid={!!emailError}
accessibilityErrorMessage={emailError}
/>
{emailError && (
<Text
accessibilityRole="alert"
accessibilityLiveRegion="polite"
className="text-red-500"
>
{emailError}
</Text>
)}
<TouchableOpacity
accessibilityRole="button"
accessibilityLabel="Submit registration form"
accessibilityState={{ disabled: !email }}
disabled={!email}
onPress={handleSubmit}
>
<Text>Submit</Text>
</TouchableOpacity>
</View>
);
}
// Toggle state
<TouchableOpacity
accessibilityRole="checkbox"
accessibilityState={{
checked: isChecked,
}}
onPress={() => setIsChecked(!isChecked)}
>
<Text>{isChecked ? '☑' : '☐'} Accept terms</Text>
</TouchableOpacity>
// Expanded state
<TouchableOpacity
accessibilityRole="button"
accessibilityState={{
expanded: isExpanded,
}}
onPress={() => setIsExpanded(!isExpanded)}
>
<Text>Show details</Text>
</TouchableOpacity>
// Selected state
<TouchableOpacity
accessibilityRole="tab"
accessibilityState={{
selected: isSelected,
}}
>
<Text>Tab 1</Text>
</TouchableOpacity>
// Busy state
<View
accessibilityRole="progressbar"
accessibilityState={{
busy: isLoading,
}}
>
<ActivityIndicator />
</View>
// Progress bar
<View
accessibilityRole="progressbar"
accessibilityValue={{
min: 0,
max: 100,
now: progress,
text: `${progress}% complete`,
}}
>
<View style={{ width: `${progress}%`, height: 4, backgroundColor: 'blue' }} />
</View>
// Slider
<Slider
accessibilityRole="adjustable"
accessibilityValue={{
min: 0,
max: 100,
now: volume,
text: `Volume ${volume}%`,
}}
accessibilityLabel="Volume control"
value={volume}
onValueChange={setVolume}
/>
// Announce changes to screen readers
<View
accessibilityLiveRegion="polite" // or "assertive"
accessibilityRole="alert"
>
<Text>{statusMessage}</Text>
</View>
// Toast/notification
function Toast({ message, visible }: ToastProps): React.ReactElement | null {
if (!visible) return null;
return (
<View
accessibilityRole="alert"
accessibilityLiveRegion="assertive"
className="bg-black p-4 rounded-lg"
>
<Text className="text-white">{message}</Text>
</View>
);
}
// Group related elements
<View
accessible={true}
accessibilityLabel="Product: iPhone 15 Pro, Price: $999"
>
<Text>iPhone 15 Pro</Text>
<Text>$999</Text>
</View>
// Prevent grouping
<View accessible={false}>
<TouchableOpacity accessibilityLabel="Edit">
<Icon name="edit" />
</TouchableOpacity>
<TouchableOpacity accessibilityLabel="Delete">
<Icon name="delete" />
</TouchableOpacity>
</View>
import { useRef } from 'react';
import { AccessibilityInfo, findNodeHandle } from 'react-native';
function FocusExample(): React.ReactElement {
const headerRef = useRef<Text>(null);
const focusOnHeader = () => {
const node = findNodeHandle(headerRef.current);
if (node) {
AccessibilityInfo.setAccessibilityFocus(node);
}
};
return (
<View>
<Text ref={headerRef} accessibilityRole="header">
Welcome
</Text>
<TouchableOpacity onPress={focusOnHeader}>
<Text>Focus header</Text>
</TouchableOpacity>
</View>
);
}
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 (
<View>
{isScreenReaderEnabled ? (
<Text>Detailed description for screen reader users</Text>
) : (
<Icon name="info" />
)}
</View>
);
}
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 <Animated.View style={animatedStyle} />;
}
// Tab bar with proper accessibility
function TabBar({ tabs, activeTab, onTabPress }) {
return (
<View accessibilityRole="tablist">
{tabs.map((tab, index) => (
<TouchableOpacity
key={tab.id}
accessibilityRole="tab"
accessibilityState={{ selected: activeTab === index }}
accessibilityLabel={`${tab.label}, tab ${index + 1} of ${tabs.length}`}
onPress={() => onTabPress(index)}
>
<Text>{tab.label}</Text>
</TouchableOpacity>
))}
</View>
);
}
function AccessibleList({ items }) {
return (
<FlashList
data={items}
renderItem={({ item, index }) => (
<View
accessible={true}
accessibilityLabel={`Item ${index + 1} of ${items.length}: ${item.title}`}
accessibilityHint="Double tap to view details"
>
<Text>{item.title}</Text>
</View>
)}
accessibilityRole="list"
/>
);
}
import { render, screen } from '@testing-library/react-native';
describe('Accessibility', () => {
it('has correct accessibility role', () => {
render(<SubmitButton />);
expect(screen.getByRole('button')).toBeOnTheScreen();
});
it('has accessibility label', () => {
render(<IconButton icon="heart" label="Add to favorites" />);
expect(screen.getByLabelText('Add to favorites')).toBeOnTheScreen();
});
it('announces state changes', () => {
render(<Toggle checked={true} label="Notifications" />);
expect(screen.getByRole('switch')).toHaveAccessibilityState({
checked: true,
});
});
});
accessibilityRoleaccessibilityLabel or are hiddenThis skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.