Concepts to Know

ARIA Roles and Attributes

Complete guide to implementing accessibility with ARIA roles and attributes

Empowering Accessibility with ARIA: Roles & Attributes Made Easy

ARIA (Accessible Rich Internet Applications) helps bridge accessibility gaps in custom UI components. It enables assistive technologies like screen readers to interpret complex interactions that native HTML can't always cover.

Here's your friendly deep dive into ARIA roles and attributes—what they do, why they matter, and how to use them like a pro

Common ARIA Roles

ARIA roles describe the purpose of an element to assistive technologies.

RoleWhat It MeansExample Use Case
role="button"Acts like a clickable buttonCustom button components
role="link"Functions as a hyperlinkJavaScript navigation
role="heading"Defines a heading level, with aria-level for depthDynamic headings
role="navigation"Denotes a section of navigational contentCustom navigation menus
role="checkbox"Represents a toggleable checkbox inputCustom checkbox components
role="menu" / role="menuitem"Describes a menu and its optionsDropdown menus
role="alert"Announces urgent info like errors or warningsError messages
role="dialog"Represents a dialog or modal windowModal components
role="tab" / role="tabpanel"Defines tab interface componentsTabbed interfaces
role="progressbar"Shows progress of a taskLoading indicators

Practical Examples

<!-- Custom Button -->
<div 
  role="button" 
  tabindex="0" 
  aria-pressed="false"
  onkeydown="handleKeyDown(event)"
  onclick="handleClick()"
>
  Toggle Theme
</div>

<!-- Custom Link -->
<div 
  role="link" 
  tabindex="0"
  aria-label="Visit our homepage"
  onkeydown="handleKeyDown(event)"
  onclick="navigateToHome()"
>
  <span class="icon">🏠</span>
</div>

<!-- Dynamic Heading -->
<div role="heading" aria-level="2">Section Title</div>

<!-- Navigation Menu -->
<nav role="navigation" aria-label="Main navigation">
  <ul>
    <li><a href="/">Home</a></li>
    <li><a href="/about">About</a></li>
    <li><a href="/contact">Contact</a></li>
  </ul>
</nav>

<!-- Custom Checkbox -->
<div 
  role="checkbox" 
  aria-checked="true" 
  tabindex="0"
  aria-labelledby="checkbox-label"
  onclick="toggleCheckbox()"
>
  <span id="checkbox-label">Subscribe to newsletter</span>
</div>

<!-- Dropdown Menu -->
<div role="menu" aria-label="User options">
  <div role="menuitem" tabindex="0" onclick="editProfile()">Edit Profile</div>
  <div role="menuitem" tabindex="0" onclick="settings()">Settings</div>
  <div role="menuitem" tabindex="0" onclick="logout()">Logout</div>
</div>

<!-- Alert Message -->
<div role="alert" aria-live="assertive">
  Your changes have been saved successfully!
</div>

<!-- Modal Dialog -->
<div 
  role="dialog" 
  aria-modal="true" 
  aria-labelledby="dialog-title"
  aria-describedby="dialog-description"
>
  <h2 id="dialog-title">Confirm Action</h2>
  <p id="dialog-description">Are you sure you want to delete this item?</p>
  <button onclick="confirm()">Yes</button>
  <button onclick="cancel()">No</button>
</div>

<!-- Tab Interface -->
<div role="tablist" aria-label="Product information">
  <button role="tab" aria-selected="true" aria-controls="panel1">Description</button>
  <button role="tab" aria-selected="false" aria-controls="panel2">Specifications</button>
  <button role="tab" aria-selected="false" aria-controls="panel3">Reviews</button>
</div>
<div role="tabpanel" id="panel1" aria-labelledby="tab1">Product description...</div>
<div role="tabpanel" id="panel2" aria-labelledby="tab2" hidden>Product specs...</div>
<div role="tabpanel" id="panel3" aria-labelledby="tab3" hidden>Product reviews...</div>

<!-- Progress Bar -->
<div 
  role="progressbar" 
  aria-valuenow="75" 
  aria-valuemin="0" 
  aria-valuemax="100"
  aria-label="Upload progress"
>
  <div class="progress-fill" style="width: 75%"></div>
</div>

Key ARIA Attributes

ARIA attributes describe an element's state, name, and behavior, often in real time.

AttributePurposeExample
aria-labelGives an element a name read by screen readers<button aria-label="Close dialog">✖</button>
aria-labelledbyReferences another element's ID as the label<section aria-labelledby="section-title">
aria-describedbyProvides extra context by referencing another element<input aria-describedby="username-help">
aria-hiddenHides an element from assistive tech without removing it<div aria-hidden="true">Decorative graphic</div>
aria-disabledIndicates the element is disabled (visually + functionally)<button aria-disabled="true">Submit</button>
aria-haspopupShows that a control triggers a popup (like a menu)<button aria-haspopup="true">Open Menu</button>
aria-expandedTells whether a collapsible element is open<button aria-expanded="false">Show Details</button>
aria-liveMarks a region as dynamically updated (e.g., notifications)<div aria-live="polite">New message received</div>
aria-pressedDescribes the toggled state of buttons<button aria-pressed="true">Pause</button>
aria-currentIndicates the current item in a set<a aria-current="page">Current Page</a>

Comprehensive Examples

<!-- Form with proper labeling -->
<form>
  <label for="username">Username:</label>
  <input 
    type="text" 
    id="username" 
    aria-describedby="username-help username-error"
    aria-required="true"
  />
  <div id="username-help">Enter your full username</div>
  <div id="username-error" role="alert" aria-live="polite"></div>
</form>

<!-- Section with proper labeling -->
<section aria-labelledby="section-title">
  <h2 id="section-title">Billing Information</h2>
  <p>Please provide your billing details below.</p>
</section>

<!-- Interactive component with state -->
<div class="accordion">
  <button 
    aria-expanded="false" 
    aria-controls="panel1"
    onclick="togglePanel('panel1')"
  >
    Section 1
  </button>
  <div id="panel1" aria-labelledby="panel1-button" hidden>
    Content for section 1...
  </div>
</div>

<!-- Live region for updates -->
<div aria-live="polite" aria-atomic="true">
  <span id="notification">Ready</span>
</div>

<!-- Navigation with current page -->
<nav aria-label="Main navigation">
  <ul>
    <li><a href="/" aria-current="page">Home</a></li>
    <li><a href="/about">About</a></li>
    <li><a href="/contact">Contact</a></li>
  </ul>
</nav>

Advanced ARIA Patterns

Custom Select Component

<div class="custom-select" role="combobox" aria-expanded="false" aria-haspopup="listbox">
  <button 
    role="combobox" 
    aria-expanded="false" 
    aria-haspopup="listbox"
    aria-labelledby="select-label"
    onclick="toggleSelect()"
  >
    <span id="select-label">Choose an option</span>
    <span aria-hidden="true">▼</span>
  </button>
  <ul role="listbox" aria-label="Options" hidden>
    <li role="option" aria-selected="false" onclick="selectOption('option1')">Option 1</li>
    <li role="option" aria-selected="false" onclick="selectOption('option2')">Option 2</li>
    <li role="option" aria-selected="false" onclick="selectOption('option3')">Option 3</li>
  </ul>
</div>

Search Results with Live Updates

<div role="search">
  <label for="search-input">Search:</label>
  <input 
    type="search" 
    id="search-input" 
    aria-describedby="search-results"
    oninput="performSearch(this.value)"
  />
  <div 
    id="search-results" 
    role="status" 
    aria-live="polite"
    aria-atomic="true"
  >
    Type to search...
  </div>
</div>

Error Handling

<form onsubmit="handleSubmit(event)">
  <div class="form-group">
    <label for="email">Email:</label>
    <input 
      type="email" 
      id="email" 
      aria-describedby="email-error"
      aria-invalid="false"
      onblur="validateEmail(this)"
    />
    <div 
      id="email-error" 
      role="alert" 
      aria-live="polite"
      class="error-message"
    ></div>
  </div>
</form>

JavaScript for ARIA

// Toggle accordion panel
function togglePanel(panelId) {
  const button = document.querySelector(`[aria-controls="${panelId}"]`);
  const panel = document.getElementById(panelId);
  const isExpanded = button.getAttribute('aria-expanded') === 'true';
  
  button.setAttribute('aria-expanded', !isExpanded);
  panel.hidden = isExpanded;
}

// Update live regions
function updateNotification(message) {
  const notification = document.getElementById('notification');
  notification.textContent = message;
}

// Handle keyboard navigation
function handleKeyDown(event) {
  if (event.key === 'Enter' || event.key === ' ') {
    event.preventDefault();
    event.target.click();
  }
}

// Validate form with ARIA
function validateEmail(input) {
  const errorElement = document.getElementById('email-error');
  const email = input.value;
  const isValid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
  
  input.setAttribute('aria-invalid', !isValid);
  
  if (!isValid) {
    errorElement.textContent = 'Please enter a valid email address';
  } else {
    errorElement.textContent = '';
  }
}

Best Practices

Do's

  • Use semantic HTML first, then layer ARIA only when needed
  • Test with screen readers and accessibility tools
  • Keep ARIA attributes up to date with dynamic content
  • Use aria-live sparingly to avoid overwhelming users
  • Provide fallbacks for complex interactions

Don'ts

  • Don't overuse ARIA when semantic HTML works
  • Don't rely solely on ARIA for functionality
  • Don't forget keyboard navigation
  • Don't use ARIA to fix bad HTML structure

Testing Tools

ToolWhat It Does
Screen ReadersNVDA (Windows), VoiceOver (Mac), JAWS (Windows)
Browser Extensionsaxe DevTools, Lighthouse, WAVE
Manual TestingKeyboard-only navigation, color contrast checkers
Automated TestingJest-axe, Cypress-axe, Playwright accessibility

Final Tips

  • Use semantic HTML first, then layer ARIA only when needed
  • Always test with screen readers and accessibility tools like Axe, Lighthouse, or VoiceOver
  • Don't forget tabindex="0" for focusable custom elements
  • Keep it simple - complex ARIA can be harder to maintain than semantic HTML

With thoughtful use of ARIA roles and attributes, we can create digital experiences that are more inclusive, intuitive, and empowering for everyone


Next: Web Accessibility Best Practices - Learn comprehensive accessibility guidelines for your applications.