Accessible Components & Patterns

Web Accessibility — Accessible Components & Patterns
Web Accessibility (a11y)
Chapter 8 · Accessible Components & Patterns

🧩 Accessible Components & Patterns

Chapters 2 through 7 covered individual techniques. This chapter builds three genuinely correct interactive components end-to-end — a modal, a combobox, and tabs — following the ARIA Authoring Practices Guide (APG), the reference documentation for exactly this kind of pattern.

The ARIA Authoring Practices Guide

A W3C resource documenting the correct keyboard model, roles, and states for common UI patterns. Checking the APG's pattern for a component is the right starting point when building from scratch — it exists precisely because these patterns require hand-built ARIA and JavaScript, exactly Chapter 3's "no native element covers this" case.

🪟 Component 1: Accessible Modal

Completing Chapter 4's Focus Trap

<div role="dialog" aria-modal="true" aria-labelledby="dialog-title"> <h2 id="dialog-title">Confirm deletion</h2> <button>Cancel</button> <button>Delete</button> </div>

role="dialog" plus aria-modal="true" announces this as a modal dialog; aria-labelledby gives it an accessible name from its own heading. Combined with Chapter 4's three-step focus management (move in, trap, restore) and an Escape-key handler to close it, this is a genuinely complete reference implementation.

🔽 Component 2: Accessible Dropdown/Combobox

<input role="combobox" aria-expanded="true" aria-controls="suggestions" aria-activedescendant="option-2"> <ul id="suggestions" role="listbox"> <li id="option-1" role="option">Apple</li> <li id="option-2" role="option">Banana</li> </ul>

aria-activedescendant

Tracks which option is currently highlighted as arrow keys move through the list — without moving actual DOM focus away from the input. Real focus stays on the input the entire time.

Full Keyboard Model

Arrow keys navigate options, Enter selects the highlighted one, Escape closes the list — none of this comes from the roles alone.

📑 Component 3: Accessible Tabs

Completing Chapter 3's role="tablist" example:

Roving Tabindex

<div role="tablist"> <button role="tab" aria-selected="true" tabindex="0">Details</button> <button role="tab" aria-selected="false" tabindex="-1">Reviews</button> </div> <div role="tabpanel" aria-labelledby="tab-details">...</div>

Only the active tab has tabindex="0"; every other tab has tabindex="-1" — one Tab press enters the whole tablist, and Left/Right arrow keys move the "roving" focus between individual tabs, not Tab itself. This is the general APG convention across list-like widgets, not just tabs.

Improvising vs Following the APG

Improvised

Guessing at roles and keyboard behavior invites inconsistency with what users expect from every other tab/combobox/dialog they've ever used.

APG Pattern

A documented, battle-tested reference for exactly this component — less to get wrong, and consistent with the rest of the web.

💻 Coding Challenges

Challenge 1: Add Escape-to-Close to the Modal

Write the keydown handler that closes the modal from Component 1 when Escape is pressed, and correctly restores focus per Chapter 4's third step.

Goal: Practice completing the modal pattern with the missing keyboard piece.

→ Solution

Challenge 2: Implement Roving Tabindex for Arrow Keys

Write the JavaScript for the tabs component that, on Right Arrow, moves tabindex="0" and aria-selected="true" to the next tab (setting the previous one to tabindex="-1"/aria-selected="false") and focuses it.

Goal: Practice implementing the roving tabindex keyboard behavior, not just the static ARIA markup.

→ Solution

Challenge 3: Explain Why Focus Stays on the Combobox Input

Explain why the combobox pattern uses aria-activedescendant to track the highlighted option instead of simply moving real DOM focus to each option as the user presses arrow keys.

Goal: Practice articulating the reasoning behind this specific, non-obvious pattern.

→ Solution

⚠️ Gotcha: Getting the ARIA Half Right and Shipping a Broken Keyboard Half

This is Chapter 3's core lesson resurfacing specifically for these more complex components: it's entirely possible to add role="tablist"/role="tab" correctly, get every accessible name right, and still ship a broken experience by forgetting the arrow-key navigation the pattern requires — leaving only default Tab behavior, which cycles through every tab individually rather than the expected roving-tabindex model. The roles and states are only half of any APG pattern; the documented keyboard interaction model is the other half, and skipping it produces a component that looks correct in an accessibility tree inspection while still being genuinely broken to actually use.

🎯 What's Next

Every technique and component pattern comes together in the final chapter: Capstone: Auditing and Fixing an Inaccessible Page — walking a deliberately inaccessible example page through every fix from the course.