Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Web Accessibility

Accessibility (a11y) ensures that websites are usable by everyone, including people with visual, auditory, motor, or cognitive disabilities. This guide covers WCAG guidelines, ARIA, semantic HTML, and common mistakes — topics critical for interviews and professional development.

WCAG Guidelines Overview

The Web Content Accessibility Guidelines (WCAG 2.1) are organized around four principles:

PrincipleDescriptionExample
PerceivableContent must be presentable in ways users can perceiveAlt text for images, captions for video
OperableUI components must be operable (keyboard, voice, mouse)All interactive elements must be keyboard-accessible
UnderstandableContent and UI must be understandableClear language, predictable navigation
RobustContent must be compatible with assistive technologiesValid HTML, proper ARIA usage

Conformance Levels

  • A — Minimum (essential for some users)
  • AA — Standard (recommended; legal requirement in many jurisdictions)
  • AAA — Enhanced (highest accessibility)

Most organizations target WCAG 2.1 AA. WCAG 2.2 (released October 2023) added criteria on focus appearance, dragging, and target size.

ARIA: Roles, States, and Properties

ARIA (Accessible Rich Internet Applications) supplements HTML when native elements aren’t sufficient. It has three categories:

Roles — What is it?

<div role="navigation" aria-label="Main">
<div role="dialog" aria-modal="true" aria-labelledby="dialog-title">
<div role="alert" aria-live="assertive">
<div role="progressbar" aria-valuenow="75" aria-valuemin="0" aria-valuemax="100">

First rule of ARIA: Don’t use ARIA if a native HTML element does the job. Prefer <button> over <div role="button">, <nav> over <div role="navigation">.

States — What’s happening now?

<button aria-expanded="false">Menu</button>
<button aria-pressed="true">Like</button>
<input aria-invalid="true" aria-describedby="error-msg">
<div aria-hidden="true">Decorative content</div>

Properties — What does it mean?

<img alt="Product photo" aria-label="Close dialog">
<button aria-describedby="hint-text">Submit</button>
<li aria-selected="true">Active tab</li>
<div role="treeitem" aria-level="2" aria-setsize="10" aria-posinset="3">

Common ARIA Attributes

AttributePurposeExample Value
aria-labelProvides accessible name"Close dialog"
aria-labelledbyPoints to element providing the nameid="title"
aria-describedbyPoints to element providing descriptionid="instructions"
aria-liveAnnounces dynamic content changes"polite", "assertive"
aria-expandedWhether element is open/closed"true" / "false"
aria-modalWhether dialog traps focus"true"
aria-hiddenHides element from accessibility tree"true"

Semantic HTML for Accessibility

Semantic HTML provides built-in accessibility — use it as your first tool:

<!-- ❌ Non-semantic -->
<div onclick="go()">Click me</div>
<div class="header">
  <div class="nav"><span onclick="home()">Home</span></div>
</div>

<!-- ✅ Semantic -->
<button type="button">Click me</button>
<header>
  <nav>
    <a href="/">Home</a>
  </nav>
</header>

Key semantic elements:

  • <button> — native keyboard support, focusable, submit/submit behavior in forms
  • <nav> — landmark, screen readers can jump to it
  • <main> — primary content landmark (only one per page)
  • <article>, <section> — content landmarks with aria-labelledby implied by heading
  • <label> — associates text with form controls
  • <fieldset> + <legend> — groups related form controls
<!-- Proper form labeling -->
<label for="email">Email</label>
<input id="email" type="email" required aria-describedby="email-hint">
<span id="email-hint">We'll never share your email</span>

Keyboard Navigation

All interactive elements must be accessible via keyboard. The natural tab order follows the DOM order.

Focus Management

// Focus trap for modals (keeps focus within dialog)
function trapFocus(element) {
  const focusable = element.querySelectorAll(
    'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
  );
  const first = focusable[0];
  const last = focusable[focusable.length - 1];

  element.addEventListener('keydown', (e) => {
    if (e.key !== 'Tab') return;
    if (e.shiftKey && document.activeElement === first) {
      e.preventDefault();
      last.focus();
    } else if (!e.shiftKey && document.activeElement === last) {
      e.preventDefault();
      first.focus();
    }
  });
}

// Focus management for modals
const previousFocus = document.activeElement;
dialog.showModal();
dialog.querySelector('button').focus(); // focus first interactive element
// On close:
previousFocus.focus(); // restore focus

Tabindex Values

ValueBehavior
0Element is focusable in natural tab order
-1Programmatically focusable (via .focus()), not in tab order
1+Never use — breaks natural tab order

Screen Reader Testing

Tools

  • VoiceOver (macOS: Cmd+F5, iOS: Settings → Accessibility)
  • NVDA (Windows, free)
  • JAWS (Windows, commercial)
  • Chrome DevTools → Accessibility panel → Inspect accessibility tree

What Screen Readers Announce

  • Element roles and names
  • State changes (aria-live regions)
  • Focus movement
  • Link destinations (<a href> vs <a href="#">)

Color Contrast

WCAG AA requires minimum contrast ratios:

ElementNormal TextLarge Text (18px+ bold or 24px+)
Minimum ratio4.5:13:1
/* Use relative color to ensure contrast */
:root {
  --bg: #ffffff;
  --text: #1a1a1a; /* 12.6:1 contrast against white — passes AAA */
  --text-muted: #6b7280; /* 5.5:1 — passes AA for normal text */
}

Never rely on color alone to convey information. Use icons, patterns, or text labels alongside color:

<!-- ❌ Bad: color-only status -->
<span style="color: red;">Error</span>

<!-- ✅ Good: color + icon + text -->
<span class="error" role="alert">⚠ Error: Invalid email</span>

Common Accessibility Mistakes

MistakeFix
Using <div> as a buttonUse <button> — native keyboard support
Missing alt text on imagesAdd descriptive alt (empty alt="" for decorative images)
Auto-playing video/audioAdd controls, or allow pause
Form inputs without labelsUse <label for="id"> or aria-label
Low contrast textMinimum 4.5:1 for normal text
Trapping focus unintentionallyEnsure users can escape modals/overlays
Skip navigation missingAdd <a href="#main" class="skip-link">Skip to main</a>
Dynamic content not announcedUse aria-live regions for toasts, notifications
Focus not visibleNever use outline: none on focusable elements (use custom :focus-visible)
Click-only interactionsSupport keyboard equivalents (Enter, Space)

Interview Questions

Q: What is the first rule of ARIA? A: Don’t use ARIA if a native HTML element provides the semantics you need. A <button> is always better than <div role="button"> because it provides built-in keyboard support, focus management, and screen reader semantics without extra JavaScript.

Q: How do you test for accessibility? A: Automated tools (axe, Lighthouse) catch ~30% of issues. Manual testing includes: keyboard navigation (Tab, Enter, Escape), screen reader testing (VoiceOver, NVDA), contrast checking, and reviewing the accessibility tree in DevTools. Automated + manual testing is essential.

Q: What is the difference between aria-label and aria-labelledby? A: aria-label provides an accessible name directly as a string attribute. aria-labelledby references another element by ID whose visible text becomes the accessible name. Use aria-labelledby when the label text is already visible on screen; use aria-label for non-visible labels.

Q: How do you handle dynamic content for screen readers? A: Use aria-live regions. aria-live="polite" announces changes when the user is idle (for non-urgent updates). aria-live="assertive" interrupts immediately (for critical alerts). Wrap dynamic content (toast notifications, loading states, error messages) in an aria-live container.

References