ARIA Implementation Guide

1. Core ARIA Roles Reference

Role Category Role Name Purpose Required Attributes
Widget Roles button Clickable element triggering action Accessible name (via label/text)
Widget Roles checkbox Checkable input with 3 states aria-checked (true/false/mixed)
Widget Roles radio Single-select option in group aria-checked, group context
Widget Roles tab Tab in tab list for panels aria-selected, aria-controls
Widget Roles tablist Container for tabs Contains tab roles
Widget Roles tabpanel Content area controlled by tab aria-labelledby (tab id)
Widget Roles slider Range input control aria-valuenow, aria-valuemin, aria-valuemax
Widget Roles menuitem Option in menu Parent menu or menubar
Widget Roles menuitemcheckbox Checkable menu option aria-checked
Widget Roles menuitemradio Radio option in menu aria-checked
Composite Roles combobox Input with popup list aria-expanded, aria-controls
Composite Roles listbox List of selectable options Contains option roles
Composite Roles grid 2D data table with interaction row and gridcell children
Composite Roles tree Hierarchical list with expand/collapse treeitem children
Document Roles article Independent content composition Accessible name (recommended)
Document Roles dialog Modal or non-modal dialog window aria-labelledby or aria-label
Document Roles alertdialog Dialog containing alert message aria-labelledby, aria-describedby
Document Roles tooltip Contextual popup information Accessible name, referenced by aria-describedby
Live Region Roles alert Important time-sensitive message None (implicit aria-live="assertive")
Live Region Roles status Advisory status message None (implicit aria-live="polite")
Live Region Roles log Sequential information log None (live region behavior)
Live Region Roles timer Numerical counter or timer None (updates announced)

Example: Common ARIA role implementations

<!-- Custom button -->
<div role="button" tabindex="0" 
     onclick="handleClick()" onkeydown="handleKeydown(event)">
  Click Me
</div>

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

<!-- Tab interface -->
<div role="tablist" aria-label="Account settings">
  <button role="tab" aria-selected="true" aria-controls="panel1" id="tab1">
    Profile
  </button>
  <button role="tab" aria-selected="false" aria-controls="panel2" id="tab2">
    Security
  </button>
</div>
<div role="tabpanel" id="panel1" aria-labelledby="tab1">
  Profile content...
</div>
Warning: Use native HTML elements when available (<button>, <input>) instead of ARIA roles. First Rule of ARIA: Don't use ARIA if you can use semantic HTML.

2. ARIA States and Properties Quick Guide

Property/State Values Purpose Common Usage
aria-label String Provides accessible name when none visible aria-label="Close dialog"
aria-labelledby ID reference(s) References element(s) providing name aria-labelledby="heading1"
aria-describedby ID reference(s) References element(s) providing description aria-describedby="hint1"
aria-hidden true/false Hides element from accessibility tree aria-hidden="true" for decorative icons
aria-expanded true/false/undefined Indicates if element is expanded Accordion, dropdown, expandable sections
aria-checked true/false/mixed State of checkbox or radio aria-checked="true"
aria-selected true/false/undefined Selection state in multi-selectable Tabs, listbox options, tree items
aria-pressed true/false/mixed Toggle button pressed state aria-pressed="true" for active state
aria-disabled true/false Indicates element is disabled aria-disabled="true" (still focusable)
aria-required true/false Indicates required form field aria-required="true"
aria-invalid true/false/grammar/spelling Validation error state aria-invalid="true" with error message
aria-live off/polite/assertive Live region update priority aria-live="polite" for status updates
aria-atomic true/false Announce entire region on change aria-atomic="true" for timers
aria-relevant additions/removals/text/all Which changes to announce aria-relevant="additions text"
aria-busy true/false Region currently updating aria-busy="true" during load
aria-controls ID reference(s) Elements controlled by this element aria-controls="panel1"
aria-owns ID reference(s) Defines parent-child relationship When DOM structure doesn't reflect relationship
aria-current page/step/location/date/time/true/false Current item in set aria-current="page" for current nav link
aria-haspopup true/false/menu/listbox/tree/grid/dialog Indicates popup type aria-haspopup="menu"
aria-modal true/false Indicates modal dialog aria-modal="true" for dialogs

Example: ARIA states and properties in action

<!-- Expandable section -->
<button aria-expanded="false" aria-controls="details1">
  Show details
</button>
<div id="details1" hidden>
  Detailed content...
</div>

<!-- Form input with error -->
<input type="email" 
       id="email"
       aria-required="true"
       aria-invalid="true"
       aria-describedby="email-error">
<span id="email-error" role="alert">
  Please enter a valid email address
</span>

<!-- Loading state -->
<div aria-live="polite" aria-busy="true">
  Loading content...
</div>

3. ARIA Live Regions and Announcements

Live Region Type aria-live Value Priority Use Case
role="alert" assertive (implicit) Immediate interruption Errors, critical warnings, time-sensitive alerts
role="status" polite (implicit) Wait for pause in speech Success messages, advisory info, progress updates
aria-live="polite" polite Non-interrupting announcement Status updates, search results count, form hints
aria-live="assertive" assertive Interrupts current speech Critical errors, timer expiration, system alerts
aria-live="off" off No announcements Disabled live region (default)
role="log" polite (implicit) Sequential updates Chat messages, activity feed, console output
role="timer" off (implicit) Periodic updates (use sparingly) Countdown, stopwatch, live clock
Live Region Attribute Values Purpose
aria-atomic true/false (default: false) When true, announces entire region; when false, only changed nodes
aria-relevant additions, removals, text, all Which types of changes trigger announcements
aria-busy true/false Suppresses announcements during bulk updates

Example: Live region implementations

<!-- Alert for errors -->
<div role="alert" aria-live="assertive">
  Your session will expire in 1 minute
</div>

<!-- Status updates -->
<div role="status" aria-live="polite" aria-atomic="true">
  <span id="search-count">24 results found</span>
</div>

<!-- Chat log -->
<div role="log" aria-live="polite" aria-relevant="additions">
  <div>User1: Hello</div>
  <div>User2: Hi there</div>
</div>

<!-- Loading with aria-busy -->
<div aria-live="polite" aria-busy="true">
  <!-- Content being updated -->
</div>
<script>
  // After update completes
  element.setAttribute('aria-busy', 'false');
</script>
Note: Live regions must exist in DOM before content changes occur. Create empty live region on page load, then update its content to trigger announcements.
Warning: Overuse of aria-live="assertive" creates jarring experience. Reserve for truly critical alerts only. Use polite for most cases.

4. ARIA Labeling Techniques

Technique When to Use Priority Example
Native Label Form inputs with visible labels 1st choice (best support) <label for="name">Name:</label>
aria-labelledby Multiple elements form the label 2nd choice (flexible) aria-labelledby="id1 id2 id3"
aria-label No visible label text available 3rd choice (translation issues) aria-label="Close dialog"
aria-describedby Additional context/instructions Supplement to label aria-describedby="hint1"
title attribute Last resort (limited AT support) Avoid if possible title="Tooltip text"
Accessible Name Calculation Understanding precedence Knowledge essential aria-labelledby > aria-label > native label > content

Example: Various labeling techniques

<!-- Native label (preferred) -->
<label for="username">Username:</label>
<input type="text" id="username" name="username">

<!-- aria-labelledby (composite label) -->
<div id="billing-heading">Billing Address</div>
<div id="billing-hint">(must match credit card)</div>
<input type="text" 
       aria-labelledby="billing-heading billing-hint"
       placeholder="123 Main St">

<!-- aria-label (icon button) -->
<button aria-label="Close dialog">
  <svg aria-hidden="true"><!-- X icon --></svg>
</button>

<!-- aria-describedby (extra context) -->
<label for="password">Password:</label>
<input type="password" 
       id="password"
       aria-describedby="password-requirements">
<div id="password-requirements">
  Must be at least 8 characters with 1 number
</div>

<!-- Complex dialog labeling -->
<div role="dialog" 
     aria-labelledby="dialog-title"
     aria-describedby="dialog-desc">
  <h2 id="dialog-title">Confirm Deletion</h2>
  <p id="dialog-desc">This action cannot be undone</p>
  <!-- Dialog content -->
</div>

Label Precedence Order

  1. aria-labelledby (highest)
  2. aria-label
  3. <label> element
  4. placeholder (input only)
  5. Element content text
  6. title attribute (lowest)
Common Mistakes:
  • Using aria-label on <div> without role
  • Empty aria-label="" (use aria-hidden)
  • Conflicting labels (both aria-label and labelledby)
  • Referencing non-existent IDs

5. ARIA Best Practices and Patterns

Best Practice Guideline Rationale
First Rule of ARIA Use native HTML elements when possible Native elements have built-in semantics, keyboard support, and better compatibility
Don't Change Native Semantics Don't use role to override native HTML <button role="heading"> breaks expected behavior
Interactive ARIA Requires Keyboard All interactive roles need keyboard handling Add tabindex, Enter/Space handlers for custom controls
Don't Hide Focusable Elements Never aria-hidden="true" on focusable elements Creates confusing experience - focus on invisible element
All Interactive Elements Are Focusable Interactive elements need tabindex="0" or native focus Keyboard users must be able to reach and activate controls
Manage Focus Appropriately Move focus to opened dialogs, restore on close Keeps keyboard users oriented in document
Avoid Redundant ARIA Don't add role="button" to <button> Native elements already have implicit roles
Update States Dynamically Toggle aria-expanded, aria-checked with JS Screen readers announce state changes when attributes update
Provide Accessible Names Every interactive element needs accessible name Users must understand purpose of controls
Test With Screen Readers Automated tools catch ~30% of issues Real AT testing reveals actual user experience

Example: Good vs bad ARIA usage

<!-- ❌ BAD: Overriding native semantics -->
<button role="heading">Click me</button>

<!-- ✅ GOOD: Use semantic HTML -->
<h2><button>Click me</button></h2>

<!-- ❌ BAD: aria-hidden on focusable element -->
<button aria-hidden="true" tabindex="0">Submit</button>

<!-- ✅ GOOD: Hide decorative, not interactive -->
<button>
  <svg aria-hidden="true"><!-- icon --></svg>
  Submit
</button>

<!-- ❌ BAD: Role without keyboard support -->
<div role="button" onclick="submit()">Submit</div>

<!-- ✅ GOOD: Proper keyboard handling -->
<div role="button" 
     tabindex="0"
     onclick="submit()"
     onkeydown="if(event.key==='Enter'||event.key===' ')submit()">
  Submit
</div>

<!-- ✅ BEST: Use native button -->
<button onclick="submit()">Submit</button>
Warning: ARIA only changes semantics, not behavior. Adding role="button" doesn't make an element clickable or keyboard accessible - you must implement those features yourself.

6. ARIA Landmark Implementation

Landmark Role HTML Equivalent Usage Guidelines Labeling
role="banner" <header> (page-level only) Site-wide header at top of page (one per page) Usually unlabeled; if multiple, use aria-label
role="navigation" <nav> Site/page navigation links (2-3 max recommended) Required when multiple: aria-label
role="main" <main> Primary page content (exactly one per page) Not needed (only one allowed)
role="complementary" <aside> Supporting content related to main content Recommended: aria-label for clarity
role="contentinfo" <footer> (page-level only) Site footer with metadata (one per page) Usually unlabeled
role="search" None (must use role) Search functionality container Optional: aria-label="Site search"
role="region" <section> with accessible name Important content section (use sparingly) Required: aria-labelledby or aria-label
role="form" <form> with accessible name Form becomes landmark only when labeled Required for landmark: aria-label

Example: Complete landmark structure

<!DOCTYPE html>
<html lang="en">
<body>
  <!-- Banner landmark -->
  <header>
    <h1>Site Name</h1>
    
    <!-- Navigation landmark -->
    <nav aria-label="Primary navigation">
      <ul>
        <li><a href="/">Home</a></li>
        <li><a href="/about">About</a></li>
      </ul>
    </nav>
    
    <!-- Search landmark -->
    <div role="search">
      <form role="search" aria-label="Site search">
        <input type="search" aria-label="Search query">
        <button type="submit">Search</button>
      </form>
    </div>
  </header>
  
  <!-- Breadcrumb navigation -->
  <nav aria-label="Breadcrumb">
    <ol>
      <li><a href="/">Home</a></li>
      <li>Current Page</li>
    </ol>
  </nav>
  
  <!-- Main landmark -->
  <main id="main-content">
    <h1>Page Title</h1>
    
    <!-- Important region -->
    <section aria-labelledby="updates-heading">
      <h2 id="updates-heading">Latest Updates</h2>
      <!-- Content -->
    </section>
    
    <!-- Form landmark -->
    <form aria-label="Contact form">
      <!-- Form fields -->
    </form>
  </main>
  
  <!-- Complementary landmark -->
  <aside aria-label="Related articles">
    <h2>You might also like</h2>
    <!-- Sidebar content -->
  </aside>
  
  <!-- Contentinfo landmark -->
  <footer>
    <p>&copy; 2025 Company Name</p>
    <nav aria-label="Footer navigation">
      <!-- Footer links -->
    </nav>
  </footer>
</body>
</html>
Note: Screen reader users can navigate directly to landmarks with keyboard shortcuts (e.g., D for landmark list in NVDA). Proper landmark structure is critical for efficient navigation.

Section 2 Key Takeaways

  • First Rule of ARIA: Use native HTML elements instead of ARIA roles whenever possible
  • ARIA only changes semantics, not behavior - you must add keyboard support manually
  • Create live regions before updating their content to ensure announcements work
  • Use aria-live="polite" for most updates; reserve "assertive" for critical alerts only
  • Label all interactive elements with aria-label, aria-labelledby, or native labels
  • Never use aria-hidden="true" on focusable elements
  • Limit landmarks to 2-3 of each type maximum to avoid overwhelming users
  • Test with actual screen readers - automated tools only catch ~30% of accessibility issues