ARIA Attributes Implementation for Accessibility
ARIA (Accessible Rich Internet Applications) is a set of HTML attributes that extend element semantics for assistive technologies. It's used when native HTML semantics are insufficient — primarily for custom interactive components.
The First Rule of ARIA
Don't use ARIA if you can use native HTML. Native elements already have built-in accessibility:
<!-- Bad — ARIA workarounds on div -->
<div role="button" tabindex="0" aria-pressed="false">Toggle</div>
<!-- Good — native button -->
<button type="button">Toggle</button>
<!-- Bad -->
<div role="heading" aria-level="2">Heading</div>
<!-- Good -->
<h2>Heading</h2>
Key ARIA Attributes
Roles:
<div role="alert">Error saving</div> <!-- important message -->
<div role="status">Saved successfully</div> <!-- non-critical notification -->
<nav role="navigation" aria-label="Main">...</nav> <!-- navigation block -->
<div role="dialog" aria-modal="true">...</div> <!-- modal window -->
<ul role="listbox">...</ul> <!-- selection list -->
States:
<button aria-expanded="false" aria-controls="dropdown-menu">Menu</button>
<ul id="dropdown-menu" aria-hidden="true">...</ul>
<input type="checkbox" aria-checked="mixed"> <!-- intermediate state -->
<button aria-pressed="true">Bold</button> <!-- toggle button -->
<input aria-disabled="true"> <!-- visually disabled -->
<div aria-busy="true">Loading...</div>
Relationships:
<!-- aria-labelledby — element heading -->
<section aria-labelledby="section-title">
<h2 id="section-title">About Company</h2>
</section>
<!-- aria-describedby — additional description -->
<input id="password" type="password"
aria-describedby="password-requirements">
<p id="password-requirements">Minimum 8 characters, one digit</p>
<!-- aria-controls — controls another element -->
<button aria-controls="panel-1" aria-expanded="false">Show details</button>
<!-- aria-owns — elements not DOM children -->
<ul role="listbox" aria-owns="option-1 option-2">...</ul>
Live Regions
<!-- Automatically announced by screen reader on change -->
<div aria-live="polite" aria-atomic="true">
<!-- Polite: announce after current speech -->
Cart updated: 3 items
</div>
<div aria-live="assertive">
<!-- Immediately interrupt current speech — only for critical errors -->
Error: server connection failed
</div>
<!-- role="status" = aria-live="polite" + aria-atomic="true" -->
<p role="status">Changes saved</p>
<!-- role="alert" = aria-live="assertive" -->
<p role="alert">Form contains errors</p>
Notification System Component with ARIA
function NotificationSystem() {
const [notifications, setNotifications] = useState<Notification[]>([]);
return (
<>
{/* Region for screen reader — always in DOM */}
<div
aria-live="polite"
aria-atomic="false"
className="sr-only"
id="sr-notifications"
>
{notifications.map(n => (
<p key={n.id}>{n.message}</p>
))}
</div>
{/* Visual notifications */}
<div className="toast-container">
{notifications.map(n => (
<Toast key={n.id} notification={n} />
))}
</div>
</>
);
}
Accordion
function Accordion({ items }) {
const [openIndex, setOpenIndex] = useState<number | null>(null);
return (
<div>
{items.map((item, i) => (
<div key={item.id}>
<h3>
<button
aria-expanded={openIndex === i}
aria-controls={`panel-${i}`}
id={`header-${i}`}
onClick={() => setOpenIndex(openIndex === i ? null : i)}
>
{item.title}
</button>
</h3>
<div
id={`panel-${i}`}
role="region"
aria-labelledby={`header-${i}`}
hidden={openIndex !== i}
>
{item.content}
</div>
</div>
))}
</div>
);
}
Data Tables
<table>
<caption>Sales statistics for Q3 2024</caption>
<thead>
<tr>
<th scope="col">Product</th>
<th scope="col">Sales</th>
<th scope="col">Change</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">Product A</th>
<td>1,240</td>
<td>
<span aria-label="12% growth">+12%</span>
</td>
</tr>
</tbody>
</table>
Common Mistakes
-
aria-hidden="true"on interactive elements — they become keyboard-inaccessible - Duplicating native semantics:
<button role="button">— redundant -
aria-labelon non-semantic divs without role — doesn't work - Dynamically updating
aria-labelwithoutaria-live— screen reader won't notice
Timeline
Add ARIA to custom components in existing project: 2–4 days depending on component count.







