Skip to main content

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") when AccessibilityProvider is present
  • Disables animations when shouldDisableAnimations() returns true (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)โ€‹

  1. Enable VoiceOver: Settings โ†’ Accessibility โ†’ VoiceOver
  2. Navigate to icons and verify labels are read correctly
  3. Ensure touch targets are easily selectable

TalkBack (Android)โ€‹

  1. Enable TalkBack: Settings โ†’ Accessibility โ†’ TalkBack
  2. Verify icon labels and roles
  3. 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" />
<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>
);
}
info

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โ€‹

PropTypeDescription
configAccessibilityConfigAccessibility configuration object
childrenReactNodeChild components

Config Optionsโ€‹

OptionTypeDefaultDescription
autoLabelsbooleantrueAuto-generate accessibility labels from icon names
labelGenerator(iconName: string) => string-Custom label generator function
highContrastbooleanfalseEnable high contrast colors
respectReducedMotionbooleantrueRespect system reduced motion preference
defaultRoleAccessibilityRole'image'Default accessibility role for icons
showFocusIndicatorsbooleantrueShow focus indicators on interactive icons
minTouchTargetSizenumber44Minimum touch target size in pixels

Next Stepsโ€‹