WCAG • ARIA • A11Y
Frontend

Accessibility in Web Development

Mayur Dabhi
Mayur Dabhi
March 26, 2026
24 min read

Web accessibility (often abbreviated as a11y) ensures that websites and web applications are usable by everyone, including people with disabilities. This includes users who are blind, deaf, have motor impairments, cognitive disabilities, or use assistive technologies. Building accessible websites isn't just a nice-to-have—it's a legal requirement in many countries and simply the right thing to do.

An accessible web benefits everyone. Curb cuts designed for wheelchairs help parents with strollers. Captions help people in noisy environments. Good color contrast helps users in bright sunlight. When we design for accessibility, we create a better experience for all users.

What You'll Learn
  • Understanding WCAG guidelines and conformance levels
  • Writing semantic HTML for screen readers
  • Implementing ARIA attributes correctly
  • Creating accessible forms and error handling
  • Keyboard navigation and focus management
  • Color contrast and visual accessibility
  • Testing tools and automated accessibility checks
  • Common accessibility mistakes to avoid

Understanding Web Accessibility

Web accessibility means that websites, tools, and technologies are designed and developed so that people with disabilities can use them. More specifically, people can perceive, understand, navigate, interact with, and contribute to the Web. Web accessibility encompasses all disabilities that affect access to the Web, including auditory, cognitive, neurological, physical, speech, and visual disabilities.

The Four Principles of Accessibility (POUR) 👁️ Perceivable Information must be presentable to users ⌨️ Operable UI components must be navigable 🧠 Understandable Information must be comprehensible 🔧 Robust Content must work with assistive tech Examples: • Alt text for images • Video captions • Color contrast • Text alternatives Examples: • Keyboard navigation • Skip links • No time limits • Focus indicators Examples: • Clear error messages • Predictable UI • Input assistance • Consistent navigation Examples: • Valid HTML • ARIA support • Future-proof code • Browser compat

The POUR principles form the foundation of WCAG guidelines

WCAG Guidelines and Conformance Levels

The Web Content Accessibility Guidelines (WCAG) are the international standard for web accessibility. Currently at version 2.2, WCAG provides three conformance levels: A, AA, and AAA. Most organizations aim for WCAG 2.1 Level AA compliance, which is also required by many accessibility laws.

Level Description Requirements Legal Standard
Level A Minimum accessibility 30 success criteria Basic compliance
Level AA Acceptable accessibility 20 additional criteria Most laws require this
Level AAA Optimal accessibility 28 additional criteria Aspirational goal
Pro Tip

While AAA is the highest level, it's not always achievable for all content. Focus on meeting AA requirements, then enhance with AAA criteria where practical. Even simple improvements like better color contrast and keyboard navigation make a significant difference.

Semantic HTML: The Foundation

Semantic HTML is the cornerstone of web accessibility. Using the correct HTML elements conveys meaning and structure to assistive technologies. Screen readers rely on semantic markup to announce content appropriately and enable navigation.

Common Semantic Elements

Semantic HTML Page Structure <header> <nav> <main> <article> <section> <aside> <footer>

Semantic HTML elements provide structure and meaning for assistive technologies

semantic-structure.html
<!-- ✅ Semantic HTML - Accessible -->
<header>
  <nav 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>
</header>

<main>
  <article>
    <h1>Understanding Web Accessibility</h1>
    <p>Web accessibility ensures everyone can use the web...</p>
    
    <section>
      <h2>Why It Matters</h2>
      <p>Over 1 billion people have disabilities...</p>
    </section>
  </article>
  
  <aside aria-label="Related content">
    <h2>Related Articles</h2>
    <ul>
      <li><a href="/wcag">WCAG Guidelines</a></li>
    </ul>
  </aside>
</main>

<footer>
  <p>&copy; 2026 My Website</p>
</footer>
non-semantic-structure.html
<!-- ❌ Non-Semantic HTML - Not Accessible -->
<div class="header">
  <div class="nav">
    <div class="nav-item">
      <span onclick="goTo('/')">Home</span>
    </div>
    <div class="nav-item">
      <span onclick="goTo('/about')">About</span>
    </div>
  </div>
</div>

<div class="main">
  <div class="article">
    <div class="title">Understanding Web Accessibility</div>
    <div class="text">Web accessibility ensures...</div>
    
    <div class="section">
      <div class="subtitle">Why It Matters</div>
      <div class="text">Over 1 billion people...</div>
    </div>
  </div>
  
  <div class="sidebar">
    <div class="sidebar-title">Related Articles</div>
    <div class="sidebar-item">
      <span onclick="goTo('/wcag')">WCAG Guidelines</span>
    </div>
  </div>
</div>

<div class="footer">
  <div>&copy; 2026 My Website</div>
</div>
Common Mistake

Using <div> and <span> for everything might look the same visually, but screen readers can't understand the structure. They won't announce headings, navigation landmarks, or button roles. Always use the appropriate semantic element.

ARIA: Accessible Rich Internet Applications

ARIA (Accessible Rich Internet Applications) provides additional attributes that enhance accessibility when native HTML isn't sufficient. However, the first rule of ARIA is: don't use ARIA if you can use native HTML.

ARIA Roles, States, and Properties

Roles

Define what an element is: role="button", role="alert", role="dialog"

States

Current condition: aria-expanded, aria-checked, aria-selected

Properties

Characteristics: aria-label, aria-describedby, aria-required

Live Regions

Dynamic updates: aria-live="polite", aria-live="assertive"

aria-examples.html
<!-- Custom button with ARIA -->
<div role="button" 
     tabindex="0" 
     aria-pressed="false"
     onclick="toggleButton(this)"
     onkeydown="handleKeyPress(event, this)">
  Toggle Feature
</div>

<!-- Accordion with ARIA -->
<button aria-expanded="false" 
        aria-controls="panel1"
        id="accordion1">
  Section Title
</button>
<div id="panel1" 
     role="region" 
     aria-labelledby="accordion1"
     hidden>
  Panel content goes here...
</div>

<!-- Live region for dynamic updates -->
<div aria-live="polite" aria-atomic="true" class="sr-only">
  <!-- Screen readers announce changes here -->
</div>

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

Keyboard Navigation

Many users rely solely on keyboards to navigate websites. This includes people with motor disabilities, blind users, and power users who prefer keyboard shortcuts. Every interactive element must be keyboard accessible.

Keyboard Navigation Flow Tab ⇥ Move forward Focusable Elements <a> <button> <input> tabindex="0" Next Focus Continue cycle Key Actions Enter/Space → Activate Escape → Close/Cancel Arrow Keys → Navigate ⚠️ Avoid tabindex > 0 Mouse-only handlers Removing focus outlines ✅ Best Practices Visible focus indicators Logical tab order Skip navigation links Focus Trap Keep focus inside modals until closed Use in dialogs

Understanding keyboard navigation patterns

keyboard-navigation.js
// Skip link for keyboard users
const skipLink = document.querySelector('.skip-link');
skipLink.addEventListener('click', (e) => {
  e.preventDefault();
  const main = document.querySelector('main');
  main.tabIndex = -1;
  main.focus();
});

// Focus trap for modals
function trapFocus(element) {
  const focusableElements = element.querySelectorAll(
    'a[href], button, textarea, input, select, [tabindex]:not([tabindex="-1"])'
  );
  const firstElement = focusableElements[0];
  const lastElement = focusableElements[focusableElements.length - 1];

  element.addEventListener('keydown', (e) => {
    if (e.key === 'Tab') {
      if (e.shiftKey && document.activeElement === firstElement) {
        e.preventDefault();
        lastElement.focus();
      } else if (!e.shiftKey && document.activeElement === lastElement) {
        e.preventDefault();
        firstElement.focus();
      }
    }
    
    if (e.key === 'Escape') {
      closeModal();
    }
  });
  
  firstElement.focus();
}

// Custom keyboard handler for interactive elements
function handleKeyboard(event, callback) {
  if (event.key === 'Enter' || event.key === ' ') {
    event.preventDefault();
    callback();
  }
}

Accessible Forms

Forms are often the most challenging aspect of accessibility. Users need to understand what information is required, receive clear error messages, and be able to correct mistakes easily.

accessible-form.html
<form novalidate>
  <!-- Properly labeled input -->
  <div class="form-group">
    <label for="email">
      Email Address
      <span aria-hidden="true">*</span>
    </label>
    <input 
      type="email" 
      id="email" 
      name="email"
      aria-required="true"
      aria-describedby="email-hint email-error"
      autocomplete="email"
    >
    <span id="email-hint" class="hint">
      We'll never share your email
    </span>
    <span id="email-error" class="error" role="alert" hidden>
      Please enter a valid email address
    </span>
  </div>

  <!-- Radio group with fieldset -->
  <fieldset>
    <legend>Preferred Contact Method</legend>
    <div>
      <input type="radio" id="contact-email" name="contact" value="email">
      <label for="contact-email">Email</label>
    </div>
    <div>
      <input type="radio" id="contact-phone" name="contact" value="phone">
      <label for="contact-phone">Phone</label>
    </div>
  </fieldset>

  <!-- Accessible error summary -->
  <div role="alert" aria-live="assertive" id="error-summary" hidden>
    <h2>There were errors with your submission</h2>
    <ul>
      <!-- Errors listed here with links to fields -->
    </ul>
  </div>

  <button type="submit">Submit Form</button>
</form>

Form Accessibility Checklist

Every input has an associated <label> with matching for/id
Required fields are indicated visually AND programmatically (aria-required)
Error messages use role="alert" and are linked via aria-describedby
Use <fieldset> and <legend> for grouped inputs
Include autocomplete attributes for common fields
Don't rely solely on color to indicate errors

Color Contrast and Visual Design

Color contrast is crucial for users with low vision or color blindness. WCAG requires specific contrast ratios between text and background colors to ensure readability.

WCAG Color Contrast Requirements Normal Text (< 18pt or < 14pt bold) AA 4.5:1 AAA 7:1 Large Text (≥ 18pt or ≥ 14pt bold) AA 3:1 AAA 4.5:1 UI Components (icons, borders, focus) AA 3:1 Don't Rely on Color Alone Use icons, patterns, text labels alongside color indicators

Contrast Examples

Good Contrast

This text passes WCAG AA

Ratio: 21:1 ✓
Poor Contrast

This text fails WCAG

Ratio: 2.5:1 ✗

Images and Media Accessibility

All non-text content needs text alternatives. This includes images, videos, audio, and interactive content. The type of alternative depends on the purpose of the content.

Informative Images

Images that convey information need descriptive alt text.

<!-- Good: Descriptive alt text -->
<img 
  src="chart-sales-q4.png" 
  alt="Bar chart showing Q4 sales: Product A $50k, Product B $75k, Product C $30k"
>

<!-- Good: Action-oriented for functional images -->
<img 
  src="search-icon.png" 
  alt="Search"
>

<!-- Bad: Non-descriptive -->
<img src="chart.png" alt="chart">
<img src="image1.jpg" alt="image">

Decorative Images

Images that don't add information should be hidden from screen readers.

<!-- Option 1: Empty alt (recommended) -->
<img src="decorative-border.png" alt="">

<!-- Option 2: Using role -->
<img src="background-pattern.png" alt="" role="presentation">

<!-- Option 3: CSS background (best for decoration) -->
<div class="hero" style="background-image: url('hero-bg.jpg')">
  <h1>Welcome</h1>
</div>

Complex Images

Charts, diagrams, and infographics need extended descriptions.

<figure>
  <img 
    src="org-chart.png" 
    alt="Company organizational chart"
    aria-describedby="org-chart-desc"
  >
  <figcaption>
    Company Structure as of 2026
  </figcaption>
</figure>

<div id="org-chart-desc" class="sr-only">
  The CEO reports to the Board of Directors. 
  Three VPs report to the CEO: VP of Engineering (manages 50 developers), 
  VP of Sales (manages 30 sales staff), and VP of Marketing (manages 15 marketers).
</div>

Video Accessibility

Videos need captions, transcripts, and audio descriptions.

<video controls>
  <source src="tutorial.mp4" type="video/mp4">
  
  <!-- Captions for deaf/hard of hearing -->
  <track 
    kind="captions" 
    src="captions-en.vtt" 
    srclang="en" 
    label="English"
    default
  >
  
  <!-- Audio descriptions for blind users -->
  <track 
    kind="descriptions" 
    src="descriptions-en.vtt" 
    srclang="en" 
    label="English Audio Description"
  >
  
  Your browser doesn't support video.
  <a href="tutorial-transcript.html">Read transcript</a>
</video>

<!-- Link to full transcript -->
<p><a href="tutorial-transcript.html">Full video transcript</a></p>

Testing for Accessibility

Accessibility testing should be integrated throughout the development process, not just at the end. Use a combination of automated tools, manual testing, and real user testing with assistive technologies.

Automated Testing Tools

Automated tools catch about 30-40% of accessibility issues. Use them as a first pass, but don't rely on them exclusively.

  • axe DevTools - Browser extension, most popular
  • WAVE - Visual feedback on page
  • Lighthouse - Built into Chrome DevTools
  • eslint-plugin-jsx-a11y - React linting rules
  • Pa11y - CI/CD integration
accessibility-testing.js
// Automated testing with Jest and axe-core
import { axe, toHaveNoViolations } from 'jest-axe';

expect.extend(toHaveNoViolations);

describe('Accessibility Tests', () => {
  it('should have no accessibility violations', async () => {
    const { container } = render(<MyComponent />);
    const results = await axe(container);
    expect(results).toHaveNoViolations();
  });
});

// Cypress with axe-core
describe('Page Accessibility', () => {
  beforeEach(() => {
    cy.visit('/');
    cy.injectAxe();
  });

  it('Has no detectable a11y violations', () => {
    cy.checkA11y();
  });

  it('Checks specific element', () => {
    cy.checkA11y('.main-content', {
      rules: {
        'color-contrast': { enabled: true }
      }
    });
  });
});

// Pa11y in CI/CD
// package.json script
{
  "scripts": {
    "test:a11y": "pa11y-ci --config .pa11yci.json"
  }
}

// .pa11yci.json
{
  "defaults": {
    "standard": "WCAG2AA",
    "timeout": 5000
  },
  "urls": [
    "http://localhost:3000/",
    "http://localhost:3000/about",
    "http://localhost:3000/contact"
  ]
}

Manual Testing Checklist

1

Keyboard Navigation

Navigate your entire site using only Tab, Shift+Tab, Enter, Space, and Arrow keys. Every interactive element should be reachable and usable.

2

Screen Reader Testing

Test with NVDA (Windows, free), VoiceOver (Mac/iOS, built-in), or JAWS. Listen to how your content is announced.

3

Zoom and Magnification

Zoom to 200% and 400%. Content should reflow without horizontal scrolling. No information should be cut off.

4

Color and Contrast

Test with color blindness simulators. View in grayscale. Check contrast ratios with tools like the WebAIM Contrast Checker.

Common Accessibility Mistakes

❌ Missing or Poor Alt Text

Problem: Images without alt text, or with useless alt like "image" or the filename.

Solution: Write descriptive alt text that conveys the same information as the image. Use empty alt for decorative images.

❌ Removing Focus Outlines

Problem: Using outline: none or :focus { outline: 0 } makes keyboard navigation impossible.

Solution: If you don't like the default outline, replace it with a custom visible focus indicator. Use :focus-visible to only show focus for keyboard users.

❌ Inaccessible Custom Components

Problem: Building custom dropdowns, modals, or tabs without proper ARIA, keyboard support, or focus management.

Solution: Use established accessible component libraries, or follow WAI-ARIA Authoring Practices. Test with screen readers.

❌ Auto-Playing Media

Problem: Videos or audio that play automatically can disorient users, interfere with screen readers, and cause distress.

Solution: Never auto-play media with sound. If video must auto-play, keep it muted with easy controls to pause/stop.

Legal Requirements

Accessibility isn't just good practice—it's the law in many jurisdictions:

  • ADA (USA) - Applies to places of public accommodation, including websites
  • Section 508 (USA) - Federal agencies must be accessible
  • EAA (EU) - European Accessibility Act covers digital products
  • AODA (Canada) - Ontario accessibility requirements

Non-compliance can result in lawsuits, fines, and reputation damage.

Getting Started Checklist

Quick Wins for Accessibility

  1. Add descriptive alt text to all informative images
  2. Ensure all form inputs have associated labels
  3. Don't remove focus outlines—style them instead
  4. Use semantic HTML elements (<button>, <nav>, <main>)
  5. Check color contrast ratios (4.5:1 for normal text)
  6. Add a skip-to-content link at the top of pages
  7. Test keyboard navigation through your entire site
  8. Run automated tests with axe or Lighthouse
  9. Add captions to all videos
  10. Test with a screen reader at least once
"The power of the Web is in its universality. Access by everyone regardless of disability is an essential aspect."
— Tim Berners-Lee, W3C Director and inventor of the World Wide Web

Web accessibility is not a feature—it's a fundamental aspect of good web development. By building with accessibility in mind from the start, you create better experiences for everyone. Start small, test often, and continuously improve. The web should be for everyone.

Accessibility A11y Web Standards WCAG ARIA Inclusive Design
Mayur Dabhi

Mayur Dabhi

Full Stack Developer with 5+ years of experience building accessible, scalable web applications with Laravel, React, and Next.js.