Accessibility & ARIA
Build inclusive web experiences with ARIA landmarks, keyboard navigation, and WCAG principles
What you'll learn
Use ARIA landmarks, roles, states, and properties correctly
Apply aria-label, aria-labelledby, and aria-describedby for accessible names
Use aria-live regions for dynamic content updates
Implement keyboard navigation with tabindex and focus management
Add skip links for keyboard and screen reader users
Apply WCAG principles: Perceivable, Operable, Understandable, Robust
Test accessibility with screen readers and tools
WCAG Principles (POUR)
| Principle | Meaning | Example |
|---|---|---|
| Perceivable | Content must be presentable to senses | Alt text on images, captions on video |
| Operable | UI must work with various input methods | Keyboard navigation, enough time |
| Understandable | Content and UI must be clear | Consistent navigation, error messages |
| Robust | Content works with current/future tools | Valid HTML, proper ARIA usage |
ARIA Landmarks
Use HTML semantic elements first. Add ARIA roles only when HTML semantics are insufficient.
<!-- HTML elements already provide landmarks -->
<header role="banner">...</header>
<nav role="navigation" aria-label="Main">...</nav>
<main role="main">...</main>
<aside role="complementary">...</aside>
<footer role="contentinfo">...</footer>
<!-- Use role when HTML element doesn't exist -->
<div role="search">
<input type="search" />
<button>Search</button>
</div>
aria-label, aria-labelledby, aria-describedby
<!-- aria-label on elements without visible text -->
<button aria-label="Close dialog">✕</button>
<!-- aria-labelledby: reference a visible label -->
<div role="dialog" aria-labelledby="dialog-title">
<h2 id="dialog-title">Confirm Delete</h2>
<p>Are you sure?</p>
</div>
<!-- aria-describedby: provide extended description -->
<input type="password" aria-describedby="password-hint" />
<p id="password-hint">Must be at least 8 characters with one number</p>
aria-live for Dynamic Content
Announce content changes to screen readers.
<div aria-live="polite" aria-atomic="true">
<!-- Screen reader announces changes here -->
<span>Item added to cart (3 items)</span>
</div>
| Value | Behavior |
|---|---|
off | Default — no announcement |
polite | Announce when user is idle |
assertive | Interrupt current announcement |
atomic="true" | Announce entire region, not just changed part |
Keyboard Navigation & tabindex
<!-- Natural tab order: tabindex="0" makes element focusable -->
<div tabindex="0" role="button" class="custom-button">Click Me</div>
<!-- Positive tabindex creates custom order (avoid unless necessary) -->
<input tabindex="1" />
<input tabindex="2" />
<!-- tabindex="-1" makes element programmatically focusable but not tabbable -->
<div tabindex="-1" id="focus-target">Focus me with JavaScript</div>
Don't Use Positive tabindex
Values > 0 create an unexpected tab order for keyboard users. The natural DOM order is almost always correct. Use
tabindex="0" to make a non-interactive element focusable, use tabindex="-1" for scripted focus.Skip Links
Allow keyboard users to skip repetitive navigation.
<body>
<!-- First focusable element on page -->
<a href="#main-content" class="skip-link"> Skip to main content </a>
<header>
<nav><!-- repetitive navigation --></nav>
</header>
<main id="main-content">
<!-- Primary content -->
</main>
</body>
.skip-link {
position: absolute;
top: -100%;
left: 0;
padding: 0.5rem 1rem;
background: #6366f1;
color: white;
z-index: 9999;
}
.skip-link:focus {
top: 0; /* visible when focused */
}
Make a Tab Panel Accessible
Code
html
1<div class="tabs">2<div class="tab-list">3 <button class="tab active">HTML</button>4 <button class="tab">CSS</button>5 <button class="tab">JS</button>6</div>7<div class="tab-panel active">8 <p>HTML content</p>9</div>10<div class="tab-panel">11 <p>CSS content</p>12</div>13<div class="tab-panel">14 <p>JS content</p>15</div>16</div>An accessible tab panel using semantic `role` attributes, `aria-controls`, `aria-selected`, and keyboard navigation. The preview shows how assistive technologies interpret the structure.
Key Takeaways
WCAG POUR: Perceivable, Operable, Understandable, Robust
Use semantic HTML first — ARIA only when HTML is insufficient
aria-label, aria-labelledby, aria-describedby provide accessible names
aria-live regions announce dynamic content to screen readers
tabindex="0" follows DOM order, tabindex="-1" enables scripted focus
Skip links let keyboard users bypass repetitive navigation