Accessibility

Course 3 · Ch 8
Accessibility Deep Dive
Full ARIA widget patterns, keyboard navigation beyond Tab/Enter, and how to actually test with a real screen reader

Intermediate Chapter 6 covered ARIA's first rule and the most common attributes. This chapter goes into genuinely complex interactive widgets — tabs, accordions — where building correct keyboard and screen reader behaviour requires deliberate, specific patterns rather than relying on a native element doing the work automatically.

The ARIA Authoring Practices Guide — The Reference Worth Knowing About

The W3C maintains a living reference (the ARIA Authoring Practices Guide, "APG") with exact, tested patterns for every common complex widget — tabs, accordions, comboboxes, menus, dialogs. Rather than inventing keyboard behaviour from scratch, checking the APG's published pattern for a given widget type first avoids reinventing something that's already been carefully specified and tested.

The Tabs Pattern

<div role="tablist" aria-label="Settings sections"> <button role="tab" aria-selected="true" aria-controls="panel-general" id="tab-general">General</button> <button role="tab" aria-selected="false" aria-controls="panel-privacy" id="tab-privacy" tabindex="-1">Privacy</button> </div> <div role="tabpanel" id="panel-general" aria-labelledby="tab-general">...</div> <div role="tabpanel" id="panel-privacy" aria-labelledby="tab-privacy" hidden>...</div>

Roving tabindex — The Key Technique Behind Tabs

Notice only the selected tab has tabindex="0" (the default for a button) while the unselected one has tabindex="-1" — this is the "roving tabindex" pattern: exactly one item in the group is reachable by Tab at any time, while Left/Right arrow keys (handled by your own JavaScript) move selection and update which tab has tabindex="0".

Without roving tabindex, Tab would stop on every single tab individually
Without this pattern, a keyboard user pressing Tab would land on each tab button one at a time before ever reaching the actual content — genuinely tedious for a row of even five or six tabs. Roving tabindex makes the whole tablist a single Tab stop, with arrow keys handling movement within it, matching how a native <select> already behaves.

The Accordion Pattern

<h3> <button aria-expanded="false" aria-controls="section1-content">Section 1</button> </h3> <div id="section1-content" hidden>...</div>
Recall Intermediate Course 2's details/summary — often the simpler choice
A genuine accordion with this exact ARIA pattern is appropriate when custom styling/behaviour beyond what <details>/<summary> offers is genuinely required. For a straightforward expand/collapse FAQ, the native element from Intermediate Chapter 8 gets all of this correct automatically, with far less code — worth checking whether the simpler native option is sufficient before building a custom ARIA pattern.

Common ARIA Widget Patterns at a Glance

PatternKey roles/attributesKey keyboard behaviour
Tabstablist/tab/tabpanel, aria-selectedArrow keys move between tabs, roving tabindex
Accordionaria-expanded, aria-controlsEnter/Space toggles; often just native button behaviour
Menumenu/menuitem, aria-expanded on the triggerArrow keys navigate items, Escape closes
Comboboxcombobox/listbox/option, aria-expanded, aria-activedescendantArrow keys move through suggestions, Enter selects

Testing with a Real Screen Reader

NVDA (Windows, free)
The most widely used free option for testing on Windows — genuinely worth installing and learning the basics (NVDA key + arrows for browsing) rather than relying purely on DevTools' accessibility tree.
VoiceOver (macOS/iOS, built in)
Built into every Mac and iPhone — Cmd+F5 toggles it on macOS. No separate install needed, making it the lowest-friction way to do a first real screen reader test.
JAWS (Windows, commercial)
The most commonly used screen reader among professional/enterprise users specifically — paid, but worth knowing it exists and behaves slightly differently from NVDA in some edge cases.

A Minimal Testing Routine

  • Turn on a screen reader and close your eyes (or turn off the monitor). Genuinely try to complete the page's main task using only what you hear.
  • Navigate by headings first (most screen readers have a dedicated heading-jump shortcut) — confirms the heading structure from Fundamentals Chapter 2 actually makes sense out loud.
  • Tab through every interactive element — confirm each one announces a clear name and role, and that focus order matches the visual reading order.
  • Try the actual complex widgets (any tabs, accordions, custom dropdowns) specifically — this is where most real accessibility bugs concentrate.
A custom widget that "looks right" can still be completely silent or confusing to a screen reader
Visual correctness and accessible correctness are genuinely independent — a beautifully styled custom dropdown built entirely from divs with click handlers, with zero ARIA attributes, can be entirely invisible to a screen reader user, announcing nothing useful at all despite working perfectly for a sighted mouse user. This is exactly why manual screen reader testing remains necessary even after automated tools (Intermediate Ch6) pass cleanly.

Chapter 8 Quick Reference

  • ARIA Authoring Practices Guide (APG) — the W3C's tested reference patterns; check it before inventing custom widget behaviour
  • Roving tabindex — exactly one item reachable by Tab at a time; arrow keys move within the group (tabs, menus)
  • Prefer details/summary over a custom accordion when the native element's behaviour is genuinely sufficient
  • Common patterns: tabs, accordion, menu, combobox — each with specific roles and keyboard expectations
  • Real screen readers: NVDA (Windows, free), VoiceOver (Mac/iOS, built in), JAWS (Windows, commercial)
  • Minimal testing routine: heading navigation, full Tab traversal, specifically exercise any custom widgets
  • Visual correctness ≠ accessible correctness — manual screen reader testing catches what automated tools and a sighted check both miss
  • Next chapter: performance — lazy loading, preload/prefetch, critical rendering path