DEV Community

Cover image for Stop Breaking the Web: A Dev's Guide to ARIA Accessibility
Learn Computer Academy
Learn Computer Academy

Posted on

Stop Breaking the Web: A Dev's Guide to ARIA Accessibility

πŸ”— Full Resource: This is a developer-focused summary of our complete ARIA guide. Get the full cheat sheets, advanced examples, and implementation roadmap at: LearnComputer.in - ARIA Accessibility Developer's Guide


The Bug That Costs Millions

<!-- This looks fine, works fine, but breaks for 1 billion users -->
<div class="btn" onclick="submit()">
  Submit Form
</div>

<!-- This actually works for everyone -->
<button type="submit">Submit Form</button>
Enter fullscreen mode Exit fullscreen mode

Here's the reality check: 96.3% of websites fail basic accessibility tests. Companies like Target ($6M), Domino's (Supreme Court case), and Netflix ($755K) learned this the expensive way.

ARIA TL;DR for Busy Devs

ARIA = Accessible Rich Internet Applications

It's your API between custom components and assistive technologies. Three concepts, that's it:

// Role: What is it?
role="button"

// Property: What's it like?  
aria-required="true"

// State: What's happening?
aria-expanded="false"
Enter fullscreen mode Exit fullscreen mode

The 2-Minute Accessibility Audit

# Terminal test
npm install -g @axe-core/cli
axe https://yoursite.com

# Manual tests (do these right now)
# 1. Tab through your site (no mouse)
# 2. Turn on high contrast mode
# 3. Zoom to 200%
# 4. Use VoiceOver (Mac: Cmd+F5) or NVDA (Windows: free)
Enter fullscreen mode Exit fullscreen mode

Failed any of these? Your site is broken for millions of users.

Image description

Essential ARIA Patterns (Copy-Paste Ready)

Custom Interactive Elements

<!-- Don't reinvent the wheel -->
<button>Native Button</button>

<!-- But if you must... -->
<div role="button" 
     tabindex="0"
     aria-label="Close dialog"
     onKeyPress="handleEnterSpace(event)"
     onClick="handleClick()">
  Γ—
</div>

<script>
function handleEnterSpace(e) {
  if (e.key === 'Enter' || e.key === ' ') {
    e.preventDefault();
    handleClick();
  }
}
</script>
Enter fullscreen mode Exit fullscreen mode

Form Validation That Doesn't Suck

<!-- Before: Silent failures -->
<input type="email" required>
<span class="error hidden">Invalid email</span>

<!-- After: Accessible feedback -->
<label for="email">Email Address</label>
<input id="email" 
       type="email" 
       aria-required="true"
       aria-invalid="false"
       aria-describedby="email-error">
<div id="email-error" 
     role="alert" 
     aria-live="assertive"
     class="error hidden">
  Invalid email format
</div>
Enter fullscreen mode Exit fullscreen mode

Dynamic Content Updates

<!-- Status container -->
<div id="status" role="status" aria-live="polite"></div>

<script>
// This announces to screen readers
document.getElementById('status').textContent = 'Changes saved!';

// For urgent updates
document.getElementById('status').setAttribute('aria-live', 'assertive');
</script>
Enter fullscreen mode Exit fullscreen mode

Navigation Components

<!-- Accessible dropdown -->
<nav>
  <button aria-expanded="false" 
          aria-controls="nav-menu"
          aria-haspopup="true">
    Menu
  </button>
  <ul id="nav-menu" hidden>
    <li><a href="/home">Home</a></li>
    <li><a href="/about">About</a></li>
  </ul>
</nav>
Enter fullscreen mode Exit fullscreen mode

React + ARIA (The Right Way)

import { useState, useRef, useEffect } from 'react';

function AccessibleModal({ isOpen, onClose, title, children }) {
  const modalRef = useRef(null);
  const previousFocus = useRef(null);

  useEffect(() => {
    if (isOpen) {
      previousFocus.current = document.activeElement;
      modalRef.current?.focus();
    } else {
      previousFocus.current?.focus();
    }
  }, [isOpen]);

  const handleKeyDown = (e) => {
    if (e.key === 'Escape') {
      onClose();
    }
  };

  if (!isOpen) return null;

  return (
    <div 
      className="modal-overlay"
      onClick={onClose}
    >
      <div
        ref={modalRef}
        role="dialog"
        aria-modal="true"
        aria-labelledby="modal-title"
        tabIndex={-1}
        onKeyDown={handleKeyDown}
        onClick={(e) => e.stopPropagation()}
      >
        <h2 id="modal-title">{title}</h2>
        {children}
        <button onClick={onClose}>Close</button>
      </div>
    </div>
  );
}

// Usage
<AccessibleModal 
  isOpen={showModal} 
  onClose={() => setShowModal(false)}
  title="Confirm Action"
>
  <p>Are you sure you want to delete this item?</p>
</AccessibleModal>
Enter fullscreen mode Exit fullscreen mode

Debug Your ARIA (Dev Tools Scripts)

// Find missing labels
console.log('Unlabeled inputs:', 
  Array.from(document.querySelectorAll('input, textarea, select'))
    .filter(el => !el.labels?.length && 
                  !el.getAttribute('aria-label') && 
                  !el.getAttribute('aria-labelledby'))
);

// Check for bad tabindex
console.log('Positive tabindex (avoid these):', 
  Array.from(document.querySelectorAll('[tabindex]'))
    .filter(el => el.tabIndex > 0)
);

// Find interactive elements without ARIA
console.log('Interactive divs/spans missing roles:',
  Array.from(document.querySelectorAll('div[onclick], span[onclick]'))
    .filter(el => !el.getAttribute('role'))
);
Enter fullscreen mode Exit fullscreen mode

The 7 ARIA Mistakes I See Everywhere

1. Redundant Roles

<!-- ❌ Redundant -->
<button role="button">Click me</button>

<!-- βœ… Native semantics -->
<button>Click me</button>
Enter fullscreen mode Exit fullscreen mode

2. Missing Keyboard Support

<!-- ❌ Mouse-only -->
<div role="button" onclick="handleClick()">Submit</div>

<!-- βœ… Keyboard accessible -->
<div role="button" 
     tabindex="0" 
     onclick="handleClick()"
     onkeydown="handleKeyPress(event)">Submit</div>
Enter fullscreen mode Exit fullscreen mode

3. Broken Focus Management

<!-- ❌ Focus disappears -->
<button onclick="this.remove()">Delete</button>

<!-- βœ… Focus moves logically -->
<button onclick="deleteAndFocus()">Delete</button>
Enter fullscreen mode Exit fullscreen mode

4. aria-label Overuse

<!-- ❌ Unnecessary -->
<h1 aria-label="Page Title">Page Title</h1>

<!-- βœ… Only when needed -->
<button aria-label="Close dialog">Γ—</button>
Enter fullscreen mode Exit fullscreen mode

Essential Dev Tools

Must-Have Extensions

  • axe DevTools - Automated accessibility scanner
  • WAVE - Visual accessibility evaluation
  • Lighthouse - Built into Chrome DevTools

Free Screen Readers

  • NVDA (Windows) - Download from nvaccess.org
  • VoiceOver (Mac) - Built-in, Cmd+F5 to enable
  • TalkBack (Android) - Built-in

Quick Commands

# Lighthouse accessibility audit
lighthouse https://yoursite.com --only-categories=accessibility

# Color contrast check (if you have node)
npx @adobe/leonardo-contrast-colors --bg "#ffffff" --colors "#0066cc"
Enter fullscreen mode Exit fullscreen mode

Testing Checklist for Every PR

  • [ ] Tab navigation works without mouse
  • [ ] Focus indicators are visible
  • [ ] Screen reader announces content correctly
  • [ ] Color contrast meets WCAG AA (4.5:1)
  • [ ] Form errors are announced
  • [ ] Dynamic content updates are announced
  • [ ] Custom components have proper roles
  • [ ] Skip links work

Beyond the Basics

This covers the fundamentals, but accessible development goes deeper. For production-ready implementations:

πŸ“š Get the complete guide: ARIA Accessibility - Full Developer's Guide

What you'll find:

  • 50+ ARIA roles with copy-paste examples
  • Complete attributes reference
  • Advanced widget patterns (date pickers, data tables, carousels)
  • Testing automation scripts
  • Real-world component library

The Business Reality

Accessibility isn't just about doing good (though it is good):

  • 15% of users have disabilities
  • $13 trillion annual spending power
  • SEO benefits - Google rewards accessible sites
  • Legal protection - Avoid expensive lawsuits

What's Your Experience?

Drop a comment:

  • What accessibility challenges have you hit?
  • Which tools do you swear by?
  • Any horror stories from accessibility audits?

Let's make the web work for everyone, one component at a time.

Top comments (1)

Collapse
 
amartadey profile image
Amarta Dey

Good Read