Accessibility is not just about meeting standards — it’s about ensuring that everyone, regardless of ability, can use your website. ARIA (Accessible Rich Internet Applications) attributes play a crucial role in bridging the gap between modern interactive UI components and Assistive Technologies (AT) such as screen readers, voice input systems, and braille displays.
This article explains why ARIA matters, how to apply it correctly, and how to test accessibility in practice. We’ll explore examples with tabs and accordions, two of the most common interactive UI patterns.
Why ARIA Matters
Web applications today rely heavily on dynamic content and JavaScript-driven interactions. While these experiences look seamless visually, users relying on Assistive Technologies (AT) can often get lost — because the browser’s default accessibility tree doesn’t update automatically when the DOM changes.
That’s where ARIA attributes step in: they provide semantic meaning and live state updates to ATs, ensuring your interface communicates what’s happening.
Key benefits:
- Provides semantic meaning for custom components.
- Keeps AT informed about state changes.
- Enables keyboard navigation and focus management.
- Reduces accessibility debt and improves user trust.
Core ARIA Attributes You Should Know
|
Attribute |
Purpose |
Example |
|---|---|---|
|
role |
Defines the purpose of an element for AT |
role="tablist", role="button" |
|
aria-label / aria-labelledby |
Adds an accessible name |
aria-label="Main navigation" |
|
aria-hidden |
Hides irrelevant DOM from AT |
aria-hidden="true" |
|
aria-selected, aria-expanded, aria-pressed |
Reflects current state |
aria-expanded="false" |
|
tabindex |
Controls focus order |
tabindex="0" makes an element focusable |
Tip: ARIA does not replace semantic HTML — it extends it. Always start with native elements (like <button> and <nav>) before reaching for ARIA.
Example 1: Accessible Tabs
Tabs are a classic case for ARIA. Visually, they switch between sections, but for screen readers, we must define relationships between tabs and their corresponding panels.
HTML Structure:
<div class="tabs" id="example-tabs">
<div role="tablist" aria-label="Content Tabs" class="tabs__nav">
<button
id="tab-1"
role="tab"
aria-selected="true"
aria-controls="panel-1"
tabindex="0"
class="tabs__btn tabs__btn--active"
>
Tab 1
</button>
<button
id="tab-2"
role="tab"
aria-selected="false"
aria-controls="panel-2"
tabindex="-1"
class="tabs__btn"
>
Tab 2
</button>
</div>
<div id="panel-1" role="tabpanel" aria-labelledby="tab-1" tabindex="0" class="tabs__pane tabs__pane--visible">
<p>Tab 1 content goes here.</p>
</div>
<div id="panel-2" role="tabpanel" aria-labelledby="tab-2" tabindex="0" class="tabs__pane">
<p>Tab 2 content goes here.</p>
</div>
</div>
JavaScript Behavior:
document.addEventListener('DOMContentLoaded', () => {
const tabContainers = document.querySelectorAll('.tabs');
tabContainers.forEach(container => {
const buttons = container.querySelectorAll('[role="tab"]');
const panels = container.querySelectorAll('[role="tabpanel"]');
function activateTab(newTab) {
const targetId = newTab.getAttribute('aria-controls');
buttons.forEach(btn => {
btn.classList.remove('tabs__btn--active');
btn.setAttribute('aria-selected', 'false');
btn.setAttribute('tabindex', '-1');
});
panels.forEach(panel => {
panel.classList.remove('tabs__pane--visible');
});
newTab.classList.add('tabs__btn--active');
newTab.setAttribute('aria-selected', 'true');
newTab.setAttribute('tabindex', '0');
document.getElementById(targetId).classList.add('tabs__pane--visible');
newTab.focus();
}
buttons.forEach(button => {
button.addEventListener('click', () => activateTab(button));
});
container.addEventListener('keydown', e => {
const current = document.activeElement;
if (!current.matches('[role="tab"]')) return;
const index = Array.from(buttons).indexOf(current);
let nextIndex;
if (e.key === 'ArrowRight') nextIndex = (index + 1) % buttons.length;
else if (e.key === 'ArrowLeft') nextIndex = (index - 1 + buttons.length) % buttons.length;
else return;
e.preventDefault();
activateTab(buttons[nextIndex]);
});
});
});
Why It Matters
- role="tablist" and role="tab" establish context.
- aria-controls links tab → panel.
- aria-selected updates for AT users.
- Arrow keys and tabindex ensure proper keyboard navigation.
Example 2: Accessible Accordion
Accordions often hide or show sections of content — ARIA helps signal these state changes to assistive tools.
HTML Structure:
<div class="accordion">
<button
class="accordion__trigger"
aria-expanded="false"
aria-controls="section-1"
id="accordion-1"
>
What is ARIA?
</button>
<div id="section-1" role="region" aria-labelledby="accordion-1" hidden>
<p>ARIA helps make custom UI components accessible.</p>
</div>
</div>
JavaScript Behavior:
document.addEventListener('click', e => {
if (!e.target.matches('.accordion__trigger')) return;
const trigger = e.target;
const expanded = trigger.getAttribute('aria-expanded') === 'true';
const content = document.getElementById(trigger.getAttribute('aria-controls'));
trigger.setAttribute('aria-expanded', String(!expanded));
content.hidden = expanded;
});
Key Points
- aria-expanded reflects toggle state.
- aria-controls connects trigger and panel.
- Using <button> ensures it’s focusable and responds to keyboard by default.
Common Mistakes to Avoid
|
Mistake |
Why It’s a Problem |
Fix |
|---|---|---|
|
Using <div> instead of <button> for interactive elements |
Not keyboard-focusable by default |
Always use semantic elements |
|
Forgetting aria-controls or aria-labelledby |
Breaks relationships between elements |
Add explicit references |
|
Setting aria-hidden="true" on visible elements |
Screen readers will skip visible content |
Use carefully, only for decorative content |
|
No keyboard navigation |
Excludes users relying on keyboards or AT |
Always test with Tab, Enter, Arrow keys |
Testing and Validation
Accessibility isn’t complete until you test it. Here’s how to check if your implementation works properly:
- Keyboard testing: Navigate using only Tab, Shift+Tab, Enter, Arrow keys.
- Screen reader testing: Use NVDA (Windows), VoiceOver (Mac), or JAWS to confirm correct announcements.
- Browser DevTools: In Chrome or Edge, open the “Accessibility” panel to inspect the accessibility tree.
- Automated tools: Use axe DevTools, Lighthouse, or WAVE.
Final Thoughts
Adding ARIA attributes correctly takes time, but the benefits are huge — better usability, SEO improvements, and compliance with accessibility standards (WCAG 2.1+). Most importantly, it ensures everyone can access your content, regardless of their abilities or the tools they use.
Want to see ARIA in action?
Try a live demo of Tabify, a modular tab system built with accessibility in mind