Accessibility
rn-iconify is built with accessibility in mind, providing features to make your icons usable by everyone.
Automatic Accessibility (v3.0)โ
In v3.0, IconRenderer automatically:
- Sets
accessibilityRole="image"on every icon - Generates an accessibility label from the icon name (e.g.,
mdi:homeโ "home icon") whenAccessibilityProvideris present - Disables animations when
shouldDisableAnimations()returnstrue(respects system Reduce Motion preference)
These features work out of the box โ no manual code needed. The examples below show how to customize behavior beyond the defaults.
Accessibility Labelsโ
Icons automatically generate accessibility labels based on icon names:
import { Mdi } from 'rn-iconify';
// Auto-generated label: "home icon"
<Mdi name="home" />
// Auto-generated label: "arrow-left icon"
<Mdi name="arrow-left" />
Custom Labelsโ
Override auto-generated labels with custom text:
import { Mdi } from 'rn-iconify';
<Mdi
name="arrow-left"
accessibilityLabel="Go back"
/>
<Mdi
name="dots-vertical"
accessibilityLabel="More options"
/>
<Mdi
name="close"
accessibilityLabel="Close dialog"
/>
Decorative Iconsโ
For icons that are purely decorative and should be ignored by screen readers:
import { Mdi } from 'rn-iconify';
// Hide from screen readers
<Mdi
name="star"
accessibilityRole="none"
accessibilityLabel=""
accessible={false}
/>
Touch Targetsโ
Ensure icons meet minimum touch target sizes (44x44pt recommended):
import { Mdi } from 'rn-iconify';
import { TouchableOpacity } from 'react-native';
// โ
Good - adequate touch target
<TouchableOpacity
style={{ padding: 10 }}
accessibilityRole="button"
accessibilityLabel="Settings"
>
<Mdi name="cog" size={24} />
</TouchableOpacity>
// โ Avoid - small touch target
<TouchableOpacity accessibilityRole="button">
<Mdi name="cog" size={16} />
</TouchableOpacity>
Hit Slopโ
Extend touch area without changing visual size:
import { Mdi } from 'rn-iconify';
import { Pressable } from 'react-native';
<Pressable
hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
accessibilityRole="button"
accessibilityLabel="Close"
>
<Mdi name="close" size={24} />
</Pressable>
Color Contrastโ
Ensure sufficient color contrast for visibility:
import { Mdi } from 'rn-iconify';
import { View } from 'react-native';
// โ
Good - high contrast
<View style={{ backgroundColor: 'white' }}>
<Mdi name="check" color="#000000" /> {/* Black on white */}
</View>
// โ
Good - sufficient contrast
<View style={{ backgroundColor: '#f5f5f5' }}>
<Mdi name="check" color="#333333" /> {/* Dark gray on light gray */}
</View>
// โ Avoid - low contrast
<View style={{ backgroundColor: '#f5f5f5' }}>
<Mdi name="check" color="#cccccc" /> {/* Light gray on light gray */}
</View>
High Contrast Modeโ
Support system high contrast settings:
import { Mdi, AccessibilityProvider, useAccessibility } from 'rn-iconify';
import { useColorScheme } from 'react-native';
// Option 1: Use AccessibilityProvider (recommended)
function App() {
return (
<AccessibilityProvider config={{ highContrast: true }}>
<AccessibleIcon name="home" defaultColor="#666" />
</AccessibilityProvider>
);
}
function AccessibleIcon({ name, defaultColor }: { name: string; defaultColor: string }) {
const accessibility = useAccessibility();
const colorScheme = useColorScheme();
const color = accessibility?.highContrast
? (colorScheme === 'dark' ? '#ffffff' : '#000000')
: defaultColor;
return <Mdi name={name} color={color} />;
}
// Option 2: Manual detection using available APIs
import { AccessibilityInfo } from 'react-native';
import { useEffect, useState } from 'react';
function ManualHighContrastIcon({ name, defaultColor }: { name: string; defaultColor: string }) {
const [highContrast, setHighContrast] = useState(false);
const colorScheme = useColorScheme();
useEffect(() => {
// Note: React Native doesn't have direct high contrast detection
// Use isInvertColorsEnabled as a proxy
AccessibilityInfo.isInvertColorsEnabled().then(setHighContrast);
const subscription = AccessibilityInfo.addEventListener(
'invertColorsChanged',
setHighContrast
);
return () => subscription.remove();
}, []);
const color = highContrast
? (colorScheme === 'dark' ? '#ffffff' : '#000000')
: defaultColor;
return <Mdi name={name} color={color} />;
}
Reduced Motionโ
Respect user's motion preferences:
import { Mdi } from 'rn-iconify';
import { AnimatedIcon } from 'rn-iconify/animated';
import { AccessibilityInfo } from 'react-native';
import { useEffect, useState } from 'react';
function AccessibleLoadingIcon({ name }: { name: string }) {
const [reduceMotion, setReduceMotion] = useState(false);
useEffect(() => {
AccessibilityInfo.isReduceMotionEnabled().then(setReduceMotion);
const subscription = AccessibilityInfo.addEventListener(
'reduceMotionChanged',
setReduceMotion
);
return () => subscription.remove();
}, []);
// Don't animate if user prefers reduced motion
if (reduceMotion) {
return <Mdi name={name} size={24} />;
}
return (
<AnimatedIcon animate="spin">
<Mdi name={name} size={24} />
</AnimatedIcon>
);
}
Semantic Iconsโ
Use appropriate accessibility roles:
import { Mdi } from 'rn-iconify';
import { Pressable, View } from 'react-native';
// Button icon
<Pressable accessibilityRole="button" accessibilityLabel="Delete item">
<Mdi name="delete" />
</Pressable>
// Link icon
<Pressable accessibilityRole="link" accessibilityLabel="Visit website">
<Mdi name="open-in-new" />
</Pressable>
// Image/decorative icon
<View accessibilityRole="image" accessibilityLabel="Status: Online">
<Mdi name="circle" color="green" />
</View>
State Communicationโ
Communicate icon states to screen readers:
import { Mdi } from 'rn-iconify';
import { Pressable } from 'react-native';
function ToggleButton({ isActive, onToggle }: { isActive: boolean; onToggle: () => void }) {
return (
<Pressable
onPress={onToggle}
accessibilityRole="switch"
accessibilityState={{ checked: isActive }}
accessibilityLabel="Dark mode"
>
<Mdi
name={isActive ? 'toggle-switch' : 'toggle-switch-off'}
color={isActive ? 'green' : 'gray'}
/>
</Pressable>
);
}
Icon + Text Combinationsโ
When icons accompany text, decide what screen readers should announce:
import { Mdi } from 'rn-iconify';
import { View, Text, Pressable } from 'react-native';
// Option 1: Read text only (icon is decorative)
<Pressable accessibilityLabel="Settings">
<Mdi name="cog" accessible={false} />
<Text>Settings</Text>
</Pressable>
// Option 2: Read both (icon adds meaning)
<Pressable accessibilityLabel="Delete item permanently">
<Mdi name="delete-forever" accessibilityLabel="Permanent" />
<Text>Delete</Text>
</Pressable>
Loading Statesโ
Announce loading states:
import { Mdi } from 'rn-iconify';
import { AnimatedIcon } from 'rn-iconify/animated';
import { View } from 'react-native';
function LoadingIcon({ isLoading }: { isLoading: boolean }) {
return (
<View
accessibilityRole="progressbar"
accessibilityState={{ busy: isLoading }}
accessibilityLabel={isLoading ? 'Loading' : 'Loaded'}
>
{isLoading ? (
<AnimatedIcon animate="spin">
<Mdi name="loading" size={24} />
</AnimatedIcon>
) : (
<Mdi name="check" size={24} />
)}
</View>
);
}
Testing Accessibilityโ
VoiceOver (iOS)โ
- Enable VoiceOver: Settings โ Accessibility โ VoiceOver
- Navigate to icons and verify labels are read correctly
- Ensure touch targets are easily selectable
TalkBack (Android)โ
- Enable TalkBack: Settings โ Accessibility โ TalkBack
- Verify icon labels and roles
- Test navigation flow
React Native Testing Libraryโ
import { render, screen } from '@testing-library/react-native';
import { Mdi } from 'rn-iconify';
test('icon has correct accessibility label', () => {
render(<Mdi name="home" accessibilityLabel="Go to home" />);
expect(screen.getByLabelText('Go to home')).toBeTruthy();
});
Accessibility Checklistโ
- All interactive icons have accessibility labels
- Decorative icons are hidden from screen readers
- Touch targets are at least 44x44pt
- Color contrast ratio is at least 4.5:1
- Animations respect reduced motion preferences
- Loading states are announced
- Icon states (active, disabled) are communicated
Best Practicesโ
1. Meaningful Labelsโ
// โ
Good - describes action
accessibilityLabel="Close dialog"
// โ Avoid - describes icon
accessibilityLabel="X icon"
2. Context Mattersโ
// Same icon, different contexts
<Mdi name="plus" accessibilityLabel="Add new item" />
<Mdi name="plus" accessibilityLabel="Increase quantity" />
<Mdi name="plus" accessibilityLabel="Expand section" />
3. Group Related Elementsโ
<Pressable
accessibilityRole="button"
accessibilityLabel="Like this post, currently 42 likes"
>
<Mdi name="heart" accessible={false} />
<Text>42</Text>
</Pressable>
Accessibility Utilitiesโ
rn-iconify exports utility functions for building accessible components.
defaultLabelGeneratorโ
Generate accessible labels from icon names:
import { defaultLabelGenerator } from 'rn-iconify';
defaultLabelGenerator('mdi:home'); // "home icon"
defaultLabelGenerator('mdi:arrow-left'); // "arrow left icon"
defaultLabelGenerator('heroicons:user'); // "user icon"
adjustForHighContrastโ
Adjust colors for high contrast mode:
import { adjustForHighContrast } from 'rn-iconify';
// Colors with luminance > 0.5 become black, others become white
adjustForHighContrast('#333333'); // '#ffffff' (dark color โ white)
adjustForHighContrast('#cccccc'); // '#000000' (light color โ black)
meetsContrastRequirementโ
Check if colors meet WCAG contrast requirements:
import { meetsContrastRequirement } from 'rn-iconify';
// Check contrast ratio between foreground and background
const meetsAA = meetsContrastRequirement('#333333', '#ffffff', 'AA');
const meetsAAA = meetsContrastRequirement('#333333', '#ffffff', 'AAA');
console.log(meetsAA); // true (4.5:1 ratio)
console.log(meetsAAA); // true (7:1 ratio)
getHighContrastAlternativeโ
Get high contrast color alternative:
import { getHighContrastAlternative } from 'rn-iconify';
// Returns #000000 or #ffffff based on background
const color = getHighContrastAlternative('#6366f1', '#ffffff');
console.log(color); // '#000000' (black for white background)
calculateTouchTargetPaddingโ
Calculate padding needed for minimum touch targets:
import { calculateTouchTargetPadding } from 'rn-iconify';
// For 24px icon, calculate padding to reach 48px target
const padding = calculateTouchTargetPadding(24, 48);
console.log(padding); // 12 (adds 12px padding on each side)
// Usage
<TouchableOpacity style={{ padding }}>
<Mdi name="settings" size={24} />
</TouchableOpacity>
withAccessibility Props Enhancerโ
Enhance icon props with automatic accessibility attributes. This is a simple props enhancer for use without hooks (useful for SSR or non-component contexts):
import { withAccessibility, Mdi } from 'rn-iconify';
// Enhance props with accessibility attributes
const enhancedProps = withAccessibility({
iconName: 'settings',
size: 24,
color: '#333',
isInteractive: true,
accessibilityLabel: 'Settings menu',
});
// Use enhanced props
<Mdi name="settings" size={24} {...enhancedProps} />
Function Signatureโ
function withAccessibility<P extends AccessibleIconProps>(
props: P & UseAccessibleIconInput
): P & {
accessible: boolean;
accessibilityRole: AccessibilityRole;
};
Example Usageโ
import { withAccessibility, Mdi } from 'rn-iconify';
import { TouchableOpacity } from 'react-native';
function IconButton({ iconName, onPress }) {
const props = withAccessibility({
iconName,
isInteractive: true,
accessibilityRole: 'button',
});
return (
<TouchableOpacity onPress={onPress} {...props}>
<Mdi name={iconName} size={24} />
</TouchableOpacity>
);
}
For React components, prefer using the useAccessibleIcon hook which provides full accessibility context integration including high contrast support, touch target calculation, and reduced motion detection.
AccessibilityProviderโ
Wrap your app with AccessibilityProvider for global settings:
import { AccessibilityProvider } from 'rn-iconify';
function App() {
return (
<AccessibilityProvider
config={{
highContrast: false,
respectReducedMotion: true,
minTouchTargetSize: 48,
autoLabels: true,
}}
>
<MainApp />
</AccessibilityProvider>
);
}
Provider Propsโ
| Prop | Type | Description |
|---|---|---|
config | AccessibilityConfig | Accessibility configuration object |
children | ReactNode | Child components |
Config Optionsโ
| Option | Type | Default | Description |
|---|---|---|---|
autoLabels | boolean | true | Auto-generate accessibility labels from icon names |
labelGenerator | (iconName: string) => string | - | Custom label generator function |
highContrast | boolean | false | Enable high contrast colors |
respectReducedMotion | boolean | true | Respect system reduced motion preference |
defaultRole | AccessibilityRole | 'image' | Default accessibility role for icons |
showFocusIndicators | boolean | true | Show focus indicators on interactive icons |
minTouchTargetSize | number | 44 | Minimum touch target size in pixels |
Next Stepsโ
- Theme Provider - Global accessibility settings
- Animations - Motion and reduced motion
- React Navigation - Accessible navigation icons