Accessibility Engineering, WCAG 2.2 & ARIA Patterns
Senior accessibility engineering goes beyond simply adding alt tags to images; it involves designing resilient, keyboard-operable widget patterns according to the W3C ARIA Authoring Practices Guide (APG), implementing focus trapping and focus restoration in complex modal systems, supporting high-contrast modes, and automating regression testing with axe-core.
In this lesson, we explore the Accessible Name & Description Computation (AccName 1.2) algorithm, focus management, complex composite keyboard interactions (Roving tabindex), and WCAG 2.2 Level AA compliance.
┌────────────────────────────────────────────────────────────┐
│ Accessible Name Computation Priority │
├────────────────────────────────────────────────────────────┤
│ 1. `aria-labelledby` (Highest Priority - References ID) │
│ ▲ │
│ 2. `aria-label` (Direct string override) │
│ ▲ │
│ 3. Native Attributes (`<label>`, `alt`, `placeholder`) │
│ ▲ │
│ 4. Subtree Text (Text content inside the element) │
│ ▲ │
│ 5. `title` Attribute (Lowest Priority Fallback) │
└────────────────────────────────────────────────────────────┘
1. Accessible Name and Description Computation (AccName)
Every interactive element in the Accessibility Tree must have an Accessible Name. Assistive technologies calculate this name using a strict precedence order defined by the W3C AccName specification:
<!-- Example 1: aria-label overrides inner button text -->
<button aria-label="Add item to shopping bag">
<span aria-hidden="true">+</span>
</button>
<!-- Example 2: aria-labelledby combines multiple element texts -->
<div role="region" aria-labelledby="section-title section-subtitle">
<h2 id="section-title">Billing Plans</h2>
<p id="section-subtitle">Annual enterprise subscriptions</p>
</div>
2. Advanced Focus Management: Focus Traps & Restoration
When a modal dialog or full-screen drawer opens:
- Focus Capture: Initial focus must be set to the first interactive element or dialog container.
- Focus Trapping: Pressing
TaborShift+Tabmust cycle focus exclusively within the modal, never escaping to the background document. - Focus Restoration: When the modal closes (via
Escapeor Close button), focus must be programmatically restored to the original button that triggered the modal:
export class AccessibleModalController {
private openerElement: HTMLElement | null = null;
public openModal(modalElement: HTMLElement, triggerElement: HTMLElement) {
this.openerElement = triggerElement;
modalElement.setAttribute("aria-hidden", "false");
// Focus first interactive control
const firstFocusable = modalElement.querySelector<HTMLElement>(
'button, [href], input, [tabindex]:not([tabindex="-1"])'
);
firstFocusable?.focus();
}
public closeModal(modalElement: HTMLElement) {
modalElement.setAttribute("aria-hidden", "true");
// Restore focus to the original triggering button!
this.openerElement?.focus();
}
}
3. The Roving tabindex Pattern for Complex Composite Widgets
For composite widgets (tabs, toolbars, tree views, menus), tabbing should move between widgets, while arrow keys navigate within the widget:
<div role="tablist" aria-label="Account Settings">
<!-- Active tab: tabindex="0" (included in sequential tab order) -->
<button role="tab" id="tab-1" aria-selected="true" tabindex="0">
General
</button>
<!-- Inactive tabs: tabindex="-1" (navigated via ArrowLeft / ArrowRight) -->
<button role="tab" id="tab-2" aria-selected="false" tabindex="-1">
Security
</button>
<button role="tab" id="tab-3" aria-selected="false" tabindex="-1">
Billing
</button>
</div>
4. Automated Accessibility Testing with Playwright & Axe-Core
Automate accessibility audits in your continuous integration pipeline to catch 40-50% of WCAG violations automatically:
import { test, expect } from "@playwright/test";
import AxeBuilder from "@axe-core/playwright";
test("homepage passes all WCAG 2.2 AA accessibility rules", async ({ page }) => {
await page.goto("/");
const results = await new AxeBuilder({ page })
.withTags(["wcag2a", "wcag2aa", "wcag21aa", "wcag22aa"])
.analyze();
expect(results.violations).toEqual([]);
});
Summary
- The Accessible Name Computation algorithm resolves element names across ARIA, native labels, and text nodes.
- Modal dialogs must trap focus during display and restore focus to the trigger element upon dismissal.
- Roving
tabindexcoordinates arrow-key navigation within composite tablists and menus. - Automated testing with
axe-corein CI enforces strict WCAG 2.2 Level AA compliance.
Best Practices
- Always Restore Focus Upon Modal Dismissal: Prevent keyboard users from losing their navigation context.
- Implement Roving
tabindexfor Menus and Tabs: Keep global sequential Tab navigation lean. - Always Test with Screen Readers (VoiceOver / NVDA): Automated testing catches only 40-50% of a11y issues; manual testing is vital.
- Never Use
aria-hidden="true"on Focused Elements: Ensure all elements visible in the Accessibility Tree are navigable.