# WCAG Accessible UI
−Build interfaces that work for everyone. Accessibility is not an afterthought or a checkbox — it is a fundamental quality signal that affects usability for all users, including those navigating with keyboards, screen readers, switch devices, or voice control. This skill covers W3C WCAG 2.2 compliance at levels A and AA, ARIA patterns, focus management, and testing methodology.
+Build interfaces that work for everyone. Accessibility is not an afterthought or a checkbox — it is a fundamental quality signal that affects usability for all users, including those navigating with keyboards, screen readers, switch devices, or voice control. This skill covers the Web Content Accessibility Guidelines (WCAG) 2.2 (W3C Recommendation), WAI-ARIA authoring practices, keyboard navigation, screen reader testing with VoiceOver and NVDA, focus management, and inclusive design.
## When to use
- Building any user-facing UI component (forms, modals, navigation, data tables)
−- Auditing an existing application for WCAG 2.2 compliance
+- Auditing an existing application for WCAG 2.2 (A/AA) compliance
- Implementing focus management for modals, dialogs, or multi-step flows
- Adding keyboard navigation to custom interactive widgets
- Testing with screen readers (VoiceOver, NVDA, JAWS) or automated tools (axe)
@@ −20 +20 @@
## Core concepts
−### WCAG 2.2 principles (POUR)
+### WCAG 2.2 (current)
−| Principle | Requirement | Key checks |
+WCAG 2.2 was published as a W3C Recommendation (12 December 2024). It extends WCAG 2.1 by adding nine additional success criteria to address gaps for people with low vision, motor and cognitive impairments. See the W3C summary for details: https://www.w3.org/WAI/standards-guidelines/wcag/new-in-22/ and the full WCAG 2.2 Recommendation: https://www.w3.org/TR/WCAG22/.
−|-----------|------------|------------|
−| **Perceivable** | Content presentable in multiple ways | Alt text, captions, contrast, resize |
−| **Operable** | UI navigable via keyboard and alternatives | Tab order, focus visible, no traps |
−| **Understandable** | Content and behavior predictable | Labels, error messages, language |
−| **Robust** | Works across assistive technologies | Valid HTML, ARIA, semantic markup |
+Notable additions relevant to UI components (selected):
+- Focus Not Obscured (Minimum) — ensures a component that receives keyboard focus is at least partially visible (AA).
+- Focus Not Obscured (Enhanced) — ensures the component is fully visible (AAA).
−### Semantic HTML first
+- Focus Appearance — requires a focus indicator area at least as large as the area of a 2 CSS pixel thick perimeter and with sufficient contrast when visible (AAA).
−Prefer native HTML elements over ARIA roles:
+Practical implication: test keyboard focus with sticky headers/footers, overlays, and responsive viewports — when focus moves to an element it must be visible (not covered) and the visual indicator must meet the size/contrast requirement described above.
−| Instead of | Use |
+### Semantic HTML first
−|-----------|-----|
−| `<div role="button">` | `<button>` |
−| `<div role="link">` | `<a href="...">` |
−| `<div role="heading">` | `<h1>`–`<h6>` |
−| `<div role="navigation">` | `<nav>` |
−| `<div role="list">` | `<ul>` or `<ol>` |
−### Color contrast requirements
+Prefer native HTML elements over ARIA roles. Native controls provide built-in semantics, keyboard behavior, and accessibility tree support across browsers and assistive technologies.
+Examples to prefer:
+- Use <button> not <div role="button">
+- Use <a href="..."> not <div role="link">
+- Use <nav>, <main>, <header>, <footer> for page sections
−| Context | Minimum ratio | WCAG level |
+- Use <table> with <th scope> and <caption> for tabular data
−|---------|--------------|------------|
−| Normal text (< 18pt) | 4.5:1 | AA |
−| Large text (≥ 18pt or bold ≥ 14pt) | 3:1 | AA |
−| UI components and graphical objects | 3:1 | AA |
−| Enhanced contrast | 7:1 | AAA |
−## Workflow
+When ARIA is needed, follow the WAI-ARIA Recommendation (current: WAI-ARIA 1.2) and monitor WAI-ARIA 1.3 drafts for new roles/attributes before wide adoption: https://www.w3.org/TR/wai-aria-1.2/ and WAI news on 1.3 (FPWD) https://www.w3.org/WAI/news/2024-01-23/for-review-wai-aria-1-3-FPWD/.
−### 1. Implement skip-to-content link
+### ARIA 1.2 (current) and 1.3 (draft)
+- WAI-ARIA 1.2 is the current W3C Recommendation (6 June 2023). Prefer it for ARIA authoring practices.
−The first focusable element on the page:
+- WAI-ARIA 1.3 is available as a First Public Working Draft; it introduces new roles and attributes (examples in the draft include roles such as suggestion, comment, mark and attributes like aria-description and braille-related attributes). Treat 1.3 as a specification to monitor — prefer stable ARIA 1.2 patterns in production until implementations and AT support mature.
−```tsx
+### Color contrast requirements
−function SkipToContent() {
− return (
− <a
− href="#main-content"
− className="fixed left-2 top-2 z-tooltip -translate-y-full rounded-sm bg-accent px-space-md py-space-sm text-surface transition-transform focus:translate-y-0"
− >
− Skip to main content
− </a>
− );
−}
−```
+- Normal text ( < 18pt ) — minimum 4.5:1 (AA)
+- Large text ( ≥ 18pt or bold ≥ 14pt ) — minimum 3:1 (AA)
+- UI components and graphical objects — 3:1 (AA)
−### 2. Implement focus trap for modals
+- Enhanced contrast — 7:1 (AAA)
−```tsx
+### Focus, keyboard, and interaction
−import { useEffect, useRef } from "react";
+Keyboard accessibility and visible focus are foundational:
+- Every interactive element must be reachable by keyboard (Tab, Shift+Tab). Composite widgets should implement Arrow key behavior as defined in WAI-ARIA Authoring Practices where appropriate.
+- Do not remove default focus indicators (outline: none) unless you provide a visible, WCAG-compliant replacement. WCAG 2.2's Focus Appearance criterion gives specific size requirements for replacements.
−function useFocusTrap(isOpen: boolean) {
+- Ensure focus is not obscured by persistent author-created content (sticky headers/footers) — test across screen sizes.
− const containerRef = useRef<HTMLDivElement>(null);
− useEffect(() => {
+## Workflow (practical steps)
− if (!isOpen || !containerRef.current) return;
− const container = containerRef.current;
+1. Implement skip-to-content link (first focusable element).
− const focusable = container.querySelectorAll<HTMLElement>(
− 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
− );
− const first = focusable[0];
− const last = focusable[focusable.length - 1];