Introduction to ARIA — Complete Guide
In this tutorial, you will learn about Introduction to ARIA. We cover key concepts, practical examples, and best practices to help you master this topic.
ARIA (Accessible Rich Internet Applications) is a W3C specification that adds semantic information to HTML elements, filling gaps where native HTML semantics are insufficient for assistive technology users.
What You'll Learn
- What ARIA is and when to use it
- The three types of ARIA attributes: roles, states, and properties
- The first rule of ARIA: do not use ARIA if native HTML works
- Common ARIA patterns and anti-patterns
- How ARIA affects the Accessibility tree
Why It Matters
- ARIA makes custom components accessible when native HTML cannot
- Incorrect ARIA can make accessibility worse, not better
- Understanding ARIA is essential for building complex widgets
- Many accessibility audits find ARIA misuse as a top issue
Real-World Use
- A custom autocomplete search box needs combobox role and aria-expanded
- A tabbed interface uses tablist, tab, and tabpanel roles
- A progress bar widget uses role="progressbar" with aria-valuenow
- A live sports scoreboard updates with aria-live announcements
flowchart LR
A[Need Custom Widget] --> B{Can HTML Do It?}
B -->|Yes| C[Use Native HTML]
B -->|No| D[Add ARIA]
D --> E[Choose Roles]
E --> F[Add States & Properties]
F --> G[Test with Screen Reader]
Understanding ARIA
ARIA stands for Accessible Rich Internet Applications. It was created by the W3C to bridge the gap between what HTML can Express natively and what modern web applications need for accessibility.
ARIA works by modifying the accessibility tree — the data structure that browsers expose to assistive technologies. When you add ARIA attributes to an HTML element, you change how that element appears in the accessibility tree without changing its visual appearance or behavior.
The First Rule of ARIA
The most important principle is: Do not use ARIA if you can use native HTML semantics.
<!-- BAD: ARIA on native elements that already have semantics -->
<nav role="navigation">
<ul role="list">
<li role="listitem"><a role="link" href="/">Home</a></li>
</ul>
</nav>
<!-- GOOD: Let native semantics do the work -->
<nav>
<ul>
<li><a href="/">Home</a></li>
</ul>
</nav>
A <nav> element already has role="navigation" in the accessibility tree. A <ul> already has role="list`. Redundant ARIA is unnecessary code that can become outdated if not maintained correctly.
When ARIA Is Appropriate
ARIA is appropriate when:
- Building custom widgets not available in native HTML (tab panels, tree views, grid widgets)
- Providing additional context that native HTML cannot express
- Overriding implicit roles when necessary (rare)
- Adding live region announcements for dynamic content
- Indicating states like expanded, selected, or pressed
Code Example: ARIA on a Custom Toggle
<!-- Custom toggle switch with ARIA -->
<button role="switch"
aria-checked="false"
aria-label="Enable dark mode"
onclick="toggleSwitch(this)"
id="dark-mode-toggle"
style="width:60px; height:30px; border-radius:15px; background:#ccc; border:2px solid #999; cursor:pointer; position:relative;">
<span style="display:block; width:24px; height:24px; border-radius:50%; background:white; position:absolute; top:1px; left:2px; transition:left 0.2s;" id="toggle-knob"></span>
</button>
<label for="dark-mode-toggle" style="margin-left:0.5rem;">Dark Mode</label>
<script>
function toggleSwitch(button) {
const isChecked = button.getAttribute('aria-checked') === 'true';
button.setAttribute('aria-checked', !isChecked);
button.style.background = isChecked ? '#ccc' : '#4CAF50';
document.getElementById('toggle-knob').style.left = isChecked ? '2px' : '32px';
}
</script>
Expected output: A screen reader announces "Dark Mode toggle switch, not checked". Clicking changes it to "checked". The switch is fully operable via keyboard (Space to toggle) and the state change is announced.
Code Example: Live Region for Status Updates
<!-- Live region announces dynamic updates -->
<div aria-live="polite" aria-atomic="true" id="upload-status"
style="border:1px solid #ddd; padding:1rem; margin:1rem 0;">
Waiting for upload...
</div>
<button onclick="simulateUpload()">Upload File</button>
<script>
function simulateUpload() {
const status = document.getElementById('upload-status');
let progress = 0;
status.textContent = 'Upload started...';
const interval = setInterval(() => {
progress += 10;
status.textContent = `Upload progress: ${progress}%`;
if (progress >= 100) {
clearInterval(interval);
status.textContent = 'Upload complete!';
}
}, 500);
}
</script>
Expected output: Screen readers announce each status update automatically without the user navigating to the status element. aria-live="polite" waits for the user to finish their current action before announcing. aria-atomic="true" announces the entire text content, not just the changed portion.
Code Example: Complex Widget — Accordion
<div id="faq-accordion">
<h3>
<button aria-expanded="false" aria-controls="faq1-content"
id="faq1-trigger" onclick="toggleAccordion('faq1')">
What is your return policy?
</button>
</h3>
<div id="faq1-content" role="region" aria-labelledby="faq1-trigger" hidden>
<p>You can return items within 30 days of purchase.</p>
</div>
<h3>
<button aria-expanded="false" aria-controls="faq2-content"
id="faq2-trigger" onclick="toggleAccordion('faq2')">
Do you ship internationally?
</button>
</h3>
<div id="faq2-content" role="region" aria-labelledby="faq2-trigger" hidden>
<p>Yes, we ship to over 50 countries worldwide.</p>
</div>
</div>
<script>
function toggleAccordion(id) {
const content = document.getElementById(`${id}-content`);
const trigger = document.getElementById(`${id}-trigger`);
const isExpanded = trigger.getAttribute('aria-expanded') === 'true';
// Close all accordion panels
document.querySelectorAll('[role="region"]').forEach(r => r.hidden = true);
document.querySelectorAll('[aria-expanded]').forEach(b => b.setAttribute('aria-expanded', 'false'));
if (!isExpanded) {
content.hidden = false;
trigger.setAttribute('aria-expanded', 'true');
}
}
</script>
Expected output: Screen readers announce each accordion button with its expanded state. "What is your return policy, collapsed" becomes "expanded" when clicked. The region relationship connects each button to its content panel.
Common Mistakes
- Redundant ARIA — Adding
role="button"to a<button>orrole="link"to an<a>wastes code and can cause maintenance issues. - Incorrect ARIA roles — Using
role="alert"on static content causes screen readers to interrupt users unnecessarily. - Missing required ARIA states — A
comboboxneedsaria-expanded,aria-haspopup, andaria-controlsto work correctly. - ARIA that changes visual behavior — ARIA attributes do not affect visual styling. If you set
aria-hidden="true"but do not hide the element visually, sighted keyboard users can still focus it. - Using ARIA instead of fixing HTML — Adding
role="heading" aria-level="2"to a<div>is a workaround. Use<h2>instead. - Not updating ARIA states dynamically — If you set
aria-expanded="true"when a component opens but forget to update it on close, the screen reader announces the wrong state. - Overusing
aria-live— Every element witharia-livecreates constant announcements. Use it sparingly and only for content that changes without user action.
Practice Questions
- What is the first rule of ARIA? Do not use ARIA if native HTML semantics can convey the same information.
- How does ARIA affect the accessibility tree? ARIA attributes modify how elements appear in the accessibility tree without changing their visual appearance or behavior.
- What is the difference between an ARIA state and an ARIA property? States (like aria-expanded, aria-checked) change frequently with user interaction. Properties (like aria-label, aria-describedby) are less likely to change.
- What does
aria-live="polite"do? It tells screen readers to announce changes to the element but to wait until the user is idle before interrupting. - Challenge: Build a custom star rating widget (1-5 stars) that is fully accessible with ARIA. Use
role="radiogroup"androle="radio"witharia-checked. Implement keyboard navigation with Arrow keys.
FAQ
Mini Project
Build an accessible custom autocomplete search component. The component must include: a text input labeled with aria-label, a listbox that appears as the user types, aria-autocomplete="list", aria-expanded to indicate list visibility, aria-activedescendant to track the currently highlighted option, keyboard navigation (Arrow keys to move, Enter to select, Escape to close), and screen reader announcements for search results count. Test with NVDA or VoiceOver.
What's Next
Continue with Lesson 7: ARIA Roles to explore the different categories of ARIA roles and how to apply them correctly.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro