Why Accessibility?

Over 1 billion people worldwide have disabilities. Accessible websites work for everyone — including users with screen readers, keyboard-only navigation, color blindness, or motor impairments. Many countries require accessibility by law (ADA, EN 301 549, AODA).

Accessible design also improves SEO, mobile UX, and code quality.

WCAG Guidelines

The Web Content Accessibility Guidelines (WCAG) 2.2 define three conformance levels:

Level Target
A Minimum — legal baseline in some jurisdictions
AA Industry standard — most organizations aim here
AAA Enhanced — not always feasible for all content

Key principles (POUR):

  • Perceivable — content available to all senses (text alternatives, contrast, captions)
  • Operable — keyboard accessible, enough time, no seizure triggers
  • Understandable — readable, predictable, helpful error messages
  • Robust — valid markup, compatible with assistive technology

Essential Practices

Alt Text for Images

  <img src="chart.png" alt="Sales increased 40% from Q1 to Q2 2024">
<img src="decorative-line.png" alt="" role="presentation">
  
  • Informative images: Describe the content and function
  • Decorative images: Empty alt="" so screen readers skip them
  • Complex images: Link to a long description or use <figcaption>

Keyboard Navigation

All interactive elements must be reachable and operable via keyboard:

  <button>Click me</button>           <!-- naturally focusable -->
<a href="/page">Link</a>            <!-- naturally focusable -->

<!-- Custom controls need tabindex and keyboard handlers -->
<div role="button" tabindex="0"
     onclick="activate()"
     onkeydown="if(event.key==='Enter'||event.key===' ') activate()">
    Custom button
</div>
  

Focus order should follow visual order. Use :focus-visible for clear focus rings:

  :focus-visible {
    outline: 2px solid #005fcc;
    outline-offset: 2px;
}
  
  <a href="#main" class="skip-link">Skip to main content</a>
<main id="main">...</main>
  
  .skip-link {
    position: absolute;
    left: -9999px;
}
.skip-link:focus {
    left: 1rem;
    top: 1rem;
    z-index: 1000;
}
  

Form Labels and Errors

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

Never rely on placeholder text alone as a label.

Color Contrast

Text must meet contrast ratios:

  • Normal text: 4.5:1 minimum (AA)
  • Large text (18px+ or 14px+ bold): 3:1 minimum

Tools: WebAIM Contrast Checker, Lighthouse, axe.

Don’t convey information by color alone — add icons, text, or patterns.

ARIA When Needed

  <nav aria-label="Breadcrumb">
<button aria-expanded="false" aria-controls="menu" id="menuBtn">Menu</button>
<ul id="menu" aria-labelledby="menuBtn" hidden>...</ul>
<div role="alert">Form submitted successfully!</div>
  

First rule of ARIA: No ARIA is better than bad ARIA. Prefer native HTML elements first.

Common ARIA patterns:

  • aria-expanded / aria-controls for disclosure menus
  • aria-live="polite" for dynamic updates
  • aria-hidden="true" on decorative icons next to visible text

Headings and Landmarks

  • One <h1> per page
  • Don’t skip heading levels (h1h2h3)
  • Use landmark elements: <header>, <nav>, <main>, <footer>

Screen reader users often navigate by headings or landmarks — broken hierarchy disorients them.

Testing Accessibility

Method Tools
Automated axe DevTools, Lighthouse, WAVE
Manual keyboard Tab through entire page; no keyboard traps
Screen reader VoiceOver (macOS), NVDA (Windows), TalkBack (Android)
Zoom Test at 200% zoom — content should reflow, not clip

Automated tools catch ~30% of issues — manual testing is essential.

Common Mistakes

Mistake Fix
<div onclick> without keyboard support Use <button> or add tabindex + key handlers
Missing form labels Associate <label for="id"> with every input
Low contrast text Check with contrast checker
Autoplaying media Provide controls; respect prefers-reduced-motion

Troubleshooting

Focus disappears after closing modal

  • Return focus to the trigger element with trigger.focus()

Screen reader reads “clickable” on non-interactive element

  • Remove role="button" or add proper keyboard behavior

Lighthouse accessibility score low

  • Fix color contrast and missing alt text first — highest impact

Accessibility is not optional — it’s a core skill for professional web developers and a legal requirement in many contexts.