Web accessibility — often abbreviated as a11y (a-[11 letters]-y) — is the practice of designing and building websites that everyone can use, regardless of ability or disability. Over one billion people worldwide live with some form of disability, and accessibility laws like the ADA, EAA (European Accessibility Act), and Section 508 increasingly require digital compliance. Beyond legal obligation, accessible websites reach more users, improve SEO, and provide a better experience for everyone.

This guide covers the practical steps developers need to take to meet WCAG 2.1 standards, from semantic HTML and ARIA attributes to keyboard navigation and screen reader testing.

Understanding WCAG 2.1 Conformance Levels

The Web Content Accessibility Guidelines (WCAG) 2.1, published by the W3C, define three levels of conformance:

  • Level A — the bare minimum. Addresses the most critical barriers that would completely prevent some users from accessing content. Examples: providing text alternatives for images, ensuring content is navigable by keyboard.
  • Level AA — the target for most organizations and the legal standard in many jurisdictions. Adds requirements like sufficient color contrast (4.5:1 for normal text), visible focus indicators, and consistent navigation.
  • Level AAA — the gold standard. Includes enhanced contrast (7:1), sign language interpretation for media, and extended audio descriptions. Full AAA conformance is rarely required or achieved across an entire site.

Aim for Level AA as your baseline. It strikes the best balance between feasibility and inclusiveness, and it satisfies most regulatory requirements.

Semantic HTML: The Foundation of Accessibility

The single most impactful thing you can do for accessibility is write semantic HTML. Screen readers, browser features, and assistive technologies all rely on the document's semantic structure to convey meaning.

❌ Non-semantic — Div soup
<div class="header">
  <div class="nav">
    <div class="nav-item" onclick="goHome()">Home</div>
    <div class="nav-item" onclick="goAbout()">About</div>
  </div>
</div>
<div class="main">
  <div class="title">Welcome</div>
  <div class="text">Some content here...</div>
</div>
✅ Semantic — Meaningful elements
<header>
  <nav aria-label="Main navigation">
    <ul>
      <li><a href="/">Home</a></li>
      <li><a href="/about">About</a></li>
    </ul>
  </nav>
</header>
<main>
  <h1>Welcome</h1>
  <p>Some content here...</p>
</main>

Key semantic elements to use consistently:

  • <header>, <footer>, <main>, <aside>, <nav> — page landmarks
  • <h1> through <h6> — heading hierarchy (never skip levels)
  • <button> — interactive controls (not <div onclick>)
  • <a> — navigation links (not <span onclick>)
  • <ul>, <ol>, <dl> — lists and definitions
  • <table>, <th>, <td> — tabular data (with proper scope attributes)
  • <form>, <label>, <fieldset>, <legend> — form structure

ARIA Attributes: When HTML Isn't Enough

ARIA (Accessible Rich Internet Applications) attributes provide additional semantics when native HTML elements cannot express the desired meaning. The first rule of ARIA is: don't use ARIA if a native HTML element can do the job.

Essential ARIA Patterns

ARIA — Common patterns
<!-- Labeling a region -->
<nav aria-label="Footer navigation">...</nav>

<!-- Describing a button's action -->
<button aria-label="Close dialog" onclick="closeModal()">
  <svg>...</svg> <!-- icon only, no visible text -->
</button>

<!-- Live region for dynamic content -->
<div aria-live="polite" aria-atomic="true">
  3 items added to cart
</div>

<!-- Expanded/collapsed state -->
<button aria-expanded="false" aria-controls="menu-panel">
  Menu
</button>
<div id="menu-panel" hidden>...</div>

<!-- Tabs -->
<div role="tablist">
  <button role="tab" aria-selected="true" aria-controls="panel1">Tab 1</button>
  <button role="tab" aria-selected="false" aria-controls="panel2">Tab 2</button>
</div>
<div role="tabpanel" id="panel1">Content 1</div>
<div role="tabpanel" id="panel2" hidden>Content 2</div>

Common ARIA Mistakes

  • Redundant roles — adding role="button" to a <button> element is unnecessary.
  • Incorrect roles — using role="button" on a <div> without also handling keyboard events (Enter and Space).
  • Missing live regions — dynamic content that changes without a page reload (e.g., toast notifications, form validation errors) must use aria-live to announce changes to screen readers.
  • Overusing aria-hidden — hiding content from assistive technologies that sighted users can see creates an inequitable experience.

Keyboard Navigation

All interactive elements must be operable via keyboard alone. Many users — including those with motor disabilities, power users, and screen reader users — navigate entirely without a mouse.

Key Requirements

  1. Tab order must be logical. Use the natural DOM order rather than manipulating tabindex. Only use tabindex="0" to make a custom element focusable, and tabindex="-1" for programmatic focus management. Avoid positive tabindex values.
  2. Focus must be visible. Never set outline: none without providing an alternative focus indicator. CSS custom focus styles should meet the 3:1 contrast requirement in WCAG 2.2.
  3. No keyboard traps. Users must be able to navigate into and out of every component using the keyboard. Modal dialogs should trap focus while open, but release it when closed.
  4. Skip navigation links. Provide a "Skip to main content" link as the first focusable element so keyboard users can bypass repetitive navigation menus.
Skip navigation link
<body>
  <a href="#main-content" class="skip-link">
    Skip to main content
  </a>
  <header>...</header>
  <main id="main-content">...</main>
</body>

<style>
.skip-link {
  position: absolute;
  top: -100%;
  left: 0;
  padding: 0.5rem 1rem;
  background: #000;
  color: #fff;
  z-index: 1000;
}
.skip-link:focus {
  top: 0;
}
</style>

Color Contrast & Visual Design

Insufficient color contrast is the most common accessibility defect found by automated tools. WCAG 2.1 requires:

  • Normal text (under 18px or 14px bold): minimum 4.5:1 contrast ratio (AA)
  • Large text (18px+ or 14px+ bold): minimum 3:1 contrast ratio (AA)
  • UI components & graphical objects: minimum 3:1 against adjacent colors

Design tips for color accessibility:

  • Never rely on color alone to convey information. Add icons, patterns, or text labels. For example, error messages should not just be red text — include an error icon and descriptive text.
  • Test with color blindness simulators. Tools like Chrome DevTools' rendering panel let you emulate protanopia, deuteranopia, tritanopia, and achromatopsia.
  • Provide dark mode carefully. Ensure contrast ratios are maintained in both light and dark themes.

Image Accessibility: Alt Text That Works

Every <img> element must have an alt attribute. The quality of that alt text dramatically affects the screen reader experience.

Alt text examples
<!-- Informative image: describe the content -->
<img src="chart.png"
     alt="Bar chart showing Q2 revenue: Product A $45K, Product B $62K, Product C $38K">

<!-- Decorative image: empty alt -->
<img src="decorative-border.svg" alt="">

<!-- Linked image: describe the destination -->
<a href="/profile">
  <img src="avatar.jpg" alt="Your profile">
</a>

<!-- Complex image: provide extended description -->
<figure>
  <img src="architecture-diagram.png"
       alt="System architecture diagram"
       aria-describedby="arch-desc">
  <figcaption id="arch-desc">
    The system consists of three microservices communicating
    via a message queue, with a shared PostgreSQL database
    and Redis cache layer.
  </figcaption>
</figure>

Form Accessibility

Forms are where accessibility most directly impacts usability. Every input needs a programmatically associated label, errors need to be clearly communicated, and the form should be navigable and submittable via keyboard.

Accessible form pattern
<form>
  <fieldset>
    <legend>Contact Information</legend>

    <div>
      <label for="email">Email address</label>
      <input type="email" id="email" name="email"
             aria-required="true"
             aria-describedby="email-help email-error"
             aria-invalid="true">
      <span id="email-help">We'll never share your email.</span>
      <span id="email-error" role="alert">
        Please enter a valid email address.
      </span>
    </div>

    <div>
      <label for="message">Message</label>
      <textarea id="message" name="message"
                aria-required="true"></textarea>
    </div>

    <button type="submit">Send Message</button>
  </fieldset>
</form>

Form Accessibility Checklist

  1. Every input has a <label> with a matching for/id pair
  2. Required fields are indicated with aria-required="true" and visual indicators
  3. Validation errors use aria-invalid="true" and role="alert"
  4. Error messages are linked to inputs via aria-describedby
  5. Related inputs are grouped with <fieldset> and <legend>
  6. Custom selects and dropdowns implement the ARIA listbox pattern with keyboard support

Focus Management in Single-Page Applications

SPAs (React, Vue, Angular) present unique accessibility challenges because page content changes without a full page reload. When the URL changes, screen readers may not announce the new content, and keyboard focus can be lost.

  • Move focus to the new content after a route change. A common pattern is to focus the <h1> of the new page using tabindex="-1" and .focus().
  • Announce route changes using a visually hidden aria-live region that announces the new page title.
  • Return focus to triggers after modal dialogs, dropdown menus, or popovers close.
Focus management — Route change
// After SPA route change
function onRouteChange(pageTitle) {
  document.title = pageTitle;

  const heading = document.querySelector('main h1');
  if (heading) {
    heading.setAttribute('tabindex', '-1');
    heading.focus();
  }

  // Announce to screen readers
  const announcer = document.getElementById('route-announcer');
  announcer.textContent = `Navigated to ${pageTitle}`;
}

// HTML: <div id="route-announcer" aria-live="assertive"
//        class="sr-only"></div>

Testing Your Accessibility

Accessibility testing should combine automated tools, manual checks, and real user testing.

Automated Tools

  • axe DevTools — browser extension that catches ~57% of WCAG issues automatically
  • Lighthouse — built into Chrome DevTools, provides an accessibility audit score
  • eslint-plugin-jsx-a11y — catches accessibility issues in React JSX at build time
  • pa11y — CI-compatible command-line accessibility testing

Manual Testing

  1. Keyboard-only navigation: Unplug your mouse and navigate the entire site using Tab, Shift+Tab, Enter, Space, and Arrow keys
  2. Screen reader testing: Use NVDA (free, Windows), VoiceOver (macOS/iOS), or TalkBack (Android) to experience your site as a screen reader user would
  3. Zoom testing: Zoom to 200% and ensure no content is clipped, overlapping, or lost
  4. Color contrast: Use the WebAIM Contrast Checker or Chrome DevTools to verify ratios

Quick-Win Accessibility Checklist

If you are just starting with accessibility, these ten actions will have the biggest impact:

  1. Add descriptive alt text to all informative images
  2. Use semantic HTML elements instead of generic <div> and <span>
  3. Ensure all form inputs have associated <label> elements
  4. Check that color contrast meets 4.5:1 for normal text
  5. Add a "Skip to main content" link
  6. Make sure all interactive elements are keyboard accessible
  7. Provide visible focus indicators on all focusable elements
  8. Set the lang attribute on the <html> element
  9. Ensure page titles are unique and descriptive
  10. Test with a screen reader for at least 15 minutes

Summary

Web accessibility is not a feature — it is a quality standard. By writing semantic HTML, using ARIA attributes correctly, ensuring keyboard operability, maintaining proper color contrast, and testing with real assistive technologies, you create websites that work for everyone. Start with Level AA compliance, integrate automated checks into your CI pipeline, and make manual screen reader testing part of your QA process. Your users — all of them — will thank you.

Write Clean, Accessible HTML

Clean, well-structured HTML is the foundation of accessible websites. Use Pan Tool to beautify messy markup or minify production-ready code.