Accessible icons ensure that all users, including those using assistive technologies, can understand and interact with your application effectively.
The Title Prop
The title prop adds a <title> element inside the SVG, providing a text description for screen readers:
import { FaDownload } from "react-icons/fa" ;
function DownloadButton () {
return (
< button >
< FaDownload title = "Download file" />
Download
</ button >
);
}
When rendered, this creates:
< svg xmlns = "http://www.w3.org/2000/svg" >
< title > Download file </ title >
<!-- icon paths -->
</ svg >
The title element provides accessible names for screen readers and appears as a tooltip in many browsers.
Decorative vs Meaningful Icons
Icons fall into two categories that require different accessibility approaches:
Decorative Icons
Meaningful Icons
Icons that are purely visual and don’t convey unique information should be hidden from screen readers: import { FaBeer } from "react-icons/fa" ;
function Question () {
return (
< h3 >
Lets go for a < FaBeer aria-hidden = "true" /> ?
</ h3 >
);
}
The text “Lets go for a?” conveys the message, so the icon is decorative. Icons that convey information not present in text need accessible labels: import { FaTrash , FaEdit } from "react-icons/fa" ;
function Actions () {
return (
< div >
< button aria-label = "Delete item" >
< FaTrash />
</ button >
< button aria-label = "Edit item" >
< FaEdit />
</ button >
</ div >
);
}
Without text labels, these buttons need aria-label to be accessible.
When icons are used as buttons without text, provide accessible labels:
Use aria-label
Add descriptive aria-label to the button element: import { FaSearch } from "react-icons/fa" ;
function SearchButton () {
return (
< button aria-label = "Search" onClick = { () => { /* search logic */ } } >
< FaSearch />
</ button >
);
}
Add title for tooltips
Optionally include title for visual tooltip: import { FaSearch } from "react-icons/fa" ;
function SearchButton () {
return (
< button aria-label = "Search" title = "Search" >
< FaSearch />
</ button >
);
}
Hide icon from screen readers
Add aria-hidden to the icon to avoid redundancy: import { FaSearch } from "react-icons/fa" ;
function SearchButton () {
return (
< button aria-label = "Search" >
< FaSearch aria-hidden = "true" />
</ button >
);
}
Icon-Only Button (Good)
Icon-Only Button (Bad)
import { FaHeart } from "react-icons/fa" ;
function LikeButton () {
return (
< button aria-label = "Like this post" >
< FaHeart aria-hidden = "true" />
</ button >
);
}
Icons with Adjacent Text
When icons accompany visible text, hide the icon from screen readers:
import { FaDownload , FaUpload } from "react-icons/fa" ;
function FileActions () {
return (
< div >
< button >
< FaDownload aria-hidden = "true" style = { { marginRight: '8px' } } />
Download
</ button >
< button >
< FaUpload aria-hidden = "true" style = { { marginRight: '8px' } } />
Upload
</ button >
</ div >
);
}
Don’t add title or aria-label to decorative icons when text is already present - this creates redundant announcements for screen reader users.
Interactive Icon Links
For icon-based navigation links:
import { FaGithub , FaTwitter , FaLinkedin } from "react-icons/fa" ;
function SocialLinks () {
return (
< nav aria-label = "Social media links" >
< a
href = "https://github.com/username"
aria-label = "GitHub profile"
target = "_blank"
rel = "noopener noreferrer"
>
< FaGithub aria-hidden = "true" />
</ a >
< a
href = "https://twitter.com/username"
aria-label = "Twitter profile"
target = "_blank"
rel = "noopener noreferrer"
>
< FaTwitter aria-hidden = "true" />
</ a >
< a
href = "https://linkedin.com/in/username"
aria-label = "LinkedIn profile"
target = "_blank"
rel = "noopener noreferrer"
>
< FaLinkedin aria-hidden = "true" />
</ a >
</ nav >
);
}
Status Indicators
For icons indicating status, ensure the meaning is conveyed accessibly:
With ARIA Live Region
With Visually Hidden Text
import { FaCheckCircle , FaTimesCircle , FaSpinner } from "react-icons/fa" ;
function StatusMessage ({ status , message }) {
const icons = {
success: FaCheckCircle ,
error: FaTimesCircle ,
loading: FaSpinner
};
const Icon = icons [ status ];
return (
< div role = "status" aria-live = "polite" >
< Icon aria-hidden = "true" />
< span > { message } </ span >
</ div >
);
}
Icons in form fields require special consideration:
import { FaUser , FaLock , FaEnvelope } from "react-icons/fa" ;
function LoginForm () {
return (
< form >
< div >
< label htmlFor = "username" >
< FaUser aria-hidden = "true" />
Username
</ label >
< input id = "username" type = "text" />
</ div >
< div >
< label htmlFor = "email" >
< FaEnvelope aria-hidden = "true" />
Email
</ label >
< input id = "email" type = "email" />
</ div >
< div >
< label htmlFor = "password" >
< FaLock aria-hidden = "true" />
Password
</ label >
< input id = "password" type = "password" />
</ div >
</ form >
);
}
Always pair form field icons with visible labels. Icons alone are not sufficient for accessibility.
Icon-Based Navigation
For icon-heavy navigation, provide multiple ways to understand the interface:
import { FaHome , FaUser , FaCog , FaBell } from "react-icons/fa" ;
function TabNavigation () {
return (
< nav aria-label = "Main navigation" >
< ul role = "tablist" >
< li role = "presentation" >
< button role = "tab" aria-selected = "true" aria-label = "Home" >
< FaHome aria-hidden = "true" />
< span > Home </ span >
</ button >
</ li >
< li role = "presentation" >
< button role = "tab" aria-selected = "false" aria-label = "Profile" >
< FaUser aria-hidden = "true" />
< span > Profile </ span >
</ button >
</ li >
< li role = "presentation" >
< button role = "tab" aria-selected = "false" aria-label = "Settings" >
< FaCog aria-hidden = "true" />
< span > Settings </ span >
</ button >
</ li >
< li role = "presentation" >
< button role = "tab" aria-selected = "false" aria-label = "Notifications" >
< FaBell aria-hidden = "true" />
< span > Notifications </ span >
</ button >
</ li >
</ ul >
</ nav >
);
}
Color and Contrast
Never rely on color alone to convey information. Icons should be distinguishable by shape, not just color.
Ensure icons meet WCAG contrast requirements:
import { FaExclamationTriangle , FaInfoCircle } from "react-icons/fa" ;
function Alerts () {
return (
< div >
{ /* ✓ Good: Combines color with icon shape and text */ }
< div role = "alert" >
< FaExclamationTriangle
aria-hidden = "true"
color = "#dc2626"
/>
< span > Error: Please check your input </ span >
</ div >
{ /* ✓ Good: High contrast, distinctive shape */ }
< div role = "status" >
< FaInfoCircle
aria-hidden = "true"
color = "#2563eb"
/>
< span > Info: Your changes have been saved </ span >
</ div >
</ div >
);
}
Accessibility Checklist
✓ Add aria-hidden="true" ✓ Ensure adjacent text provides full context ✓ Don’t add title or aria-label
Is the icon meaningful/functional?
✓ Add aria-label to the parent interactive element ✓ Or provide visible text label ✓ Consider adding title for tooltips ✓ Add aria-hidden="true" to avoid redundancy when label is present
Is the icon in a button or link?
✓ Ensure the button/link has an accessible name ✓ Use aria-label if no visible text ✓ Add aria-hidden="true" to the icon
Does the icon convey status?
✓ Use role="status" or role="alert" ✓ Consider aria-live regions for dynamic updates ✓ Include text description, not just the icon
Does the icon meet contrast requirements?
✓ Ensure 3:1 contrast ratio for UI components (WCAG AA) ✓ Don’t rely on color alone to convey meaning ✓ Test with color blindness simulators
Testing Accessibility
Test your icon implementation with:
Screen readers : NVDA (Windows), JAWS (Windows), VoiceOver (macOS/iOS)
Keyboard navigation : Ensure all icon buttons are keyboard accessible
Browser extensions : axe DevTools, WAVE, Lighthouse
Automated testing : jest-axe, pa11y, Playwright accessibility tests
// Example: Testing with jest-axe
import { render } from '@testing-library/react' ;
import { axe , toHaveNoViolations } from 'jest-axe' ;
import { FaDownload } from 'react-icons/fa' ;
expect . extend ( toHaveNoViolations );
test ( 'download button is accessible' , async () => {
const { container } = render (
< button aria-label = "Download file" >
< FaDownload aria-hidden = "true" />
</ button >
);
const results = await axe ( container );
expect ( results ). toHaveNoViolations ();
});
Next Steps
Customizing Icons Learn how to customize icon appearance
Styling Apply global styles with IconContext