Skip to main content

HTML Accessibility - ARIA Roles and Attributes

Answer​

ARIA (Accessible Rich Internet Applications) provides attributes that enhance HTML semantics for assistive technologies like screen readers.

ARIA Categories​

Key Principles​

Essential ARIA Roles​

Landmark Roles​

<!-- Landmarks help screen reader users navigate -->
<header role="banner">
<nav role="navigation" aria-label="Main">
<!-- Navigation links -->
</nav>
</header>

<main role="main">
<article role="article">
<!-- Content -->
</article>
</main>

<aside role="complementary">
<!-- Sidebar -->
</aside>

<footer role="contentinfo">
<!-- Footer content -->
</footer>

Note: HTML5 semantic elements have implicit roles, so <header> already has role="banner". Add explicit roles only when needed.

Widget Roles​

<!-- Custom button -->
<div
role="button"
tabindex="0"
onkeydown="if(event.key==='Enter') this.click()"
>
Click Me
</div>

<!-- Tab interface -->
<div role="tablist" aria-label="Settings">
<button role="tab" aria-selected="true" aria-controls="panel1">
General
</button>
<button role="tab" aria-selected="false" aria-controls="panel2">
Privacy
</button>
</div>

<div role="tabpanel" id="panel1" aria-labelledby="tab1">
General settings content
</div>

Important ARIA Attributes​

Labels and Descriptions​

<!-- aria-label: labels element directly -->
<button aria-label="Close dialog">×</button>

<!-- aria-labelledby: references another element's text -->
<h2 id="dialog-title">Settings</h2>
<div role="dialog" aria-labelledby="dialog-title">
<!-- Dialog content -->
</div>

<!-- aria-describedby: provides additional description -->
<input type="password" aria-describedby="password-hint" />
<p id="password-hint">Must be at least 8 characters</p>

State Indicators​

<!-- Expandable section -->
<button aria-expanded="false" aria-controls="menu">Menu</button>
<ul id="menu" hidden>
<li><a href="#">Option 1</a></li>
</ul>

<!-- Checkbox state -->
<div role="checkbox" aria-checked="false" tabindex="0">Accept terms</div>

<!-- Loading state -->
<button aria-busy="true" aria-disabled="true">
<span class="spinner"></span>
Loading...
</button>

Common Patterns​

<div
role="dialog"
aria-modal="true"
aria-labelledby="modal-title"
aria-describedby="modal-desc"
>
<h2 id="modal-title">Confirm Action</h2>
<p id="modal-desc">Are you sure you want to proceed?</p>
<button>Cancel</button>
<button>Confirm</button>
</div>

Alert Messages​

<!-- Polite announcement (waits for idle) -->
<div role="status" aria-live="polite">Profile saved successfully</div>

<!-- Assertive announcement (immediate) -->
<div role="alert" aria-live="assertive">
Error: Please fix the highlighted fields
</div>

<!-- Live region that tracks changes -->
<div aria-live="polite" aria-atomic="true">
<span>3 items in cart</span>
</div>

Form Validation​

<form>
<label for="email">Email</label>
<input
type="email"
id="email"
aria-invalid="true"
aria-describedby="email-error"
required
/>
<span id="email-error" role="alert">
Please enter a valid email address
</span>
</form>

Common Mistakes​

<!-- ❌ Redundant role on native element -->
<button role="button">Click</button>

<!-- ✅ Native element, no role needed -->
<button>Click</button>

<!-- ❌ Changing native semantics -->
<h1 role="button">Heading</h1>

<!-- ✅ Use appropriate element -->
<h1>Heading</h1>
<button>Action</button>

<!-- ❌ Non-interactive element with click handler -->
<span onclick="doSomething()">Click me</span>

<!-- ✅ Button with proper accessibility -->
<button onclick="doSomething()">Click me</button>

Testing Accessibility​

// Basic keyboard trap prevention
document.addEventListener("keydown", (e) => {
if (e.key === "Tab") {
const focusable = modal.querySelectorAll(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
);
const first = focusable[0];
const last = focusable[focusable.length - 1];

if (e.shiftKey && document.activeElement === first) {
e.preventDefault();
last.focus();
} else if (!e.shiftKey && document.activeElement === last) {
e.preventDefault();
first.focus();
}
}
});

Key Points​

  • Use semantic HTML first, ARIA second
  • Every interactive element needs keyboard access
  • Manage focus for custom widgets
  • Test with actual screen readers
  • Live regions announce dynamic changes
  • Don't override native element semantics