# Accessibility (a11y) > WCAG 2.2 AA: semantic HTML, keyboard nav, and ARIA done right. Frontend architecture guidance from fearchitect, written by Abas Turabli and last reviewed 2026-06-21. Source: https://fearchitect.com/topics/accessibility Use it as reference for the task at hand. Before changing code, check this guidance against the codebase: where the code already makes a different, deliberate choice, flag the conflict instead of rewriting it. Library APIs move faster than this guide, so confirm exact signatures in the official docs linked at the end. ## Summary Accessibility means every user — regardless of motor, visual, or cognitive ability — can perceive, operate, and understand your UI. WCAG 2.2 AA is the legal bar: four principles (POUR) cover 50 success criteria. Start with semantic HTML; reach for ARIA only when native elements cannot express the state. Axe catches ~57% automatically; the rest need manual screen-reader testing. ## Accessibility (a11y) A UI is accessible when every user — including those who use a keyboard only, a screen reader, or switch access — can perceive, operate, and understand it. WCAG 2.2 AA defines the bar: 50 success criteria grouped under four principles (POUR). Passing AA is required by law in many jurisdictions (ADA, EN 301 549) and improves usability for all users. The concrete starting point is correct semantic HTML; ARIA fills in what HTML cannot express. ## The POUR principles at a glance - Perceivable: content reaches at least one sense — text alternatives, captions, and contrast ≥4.5:1 at AA. - Operable: every interaction works via keyboard alone; no content flashes more than 3 Hz. - Understandable: language is declared, errors are described, consistent navigation across pages. - R (4th principle): valid markup and correct ARIA roles/states so assistive tech can parse the tree. ## Accessible disclosure button with focus management A disclosure widget (show/hide) is the canonical ARIA pattern: a ` ); } ``` `aria-expanded` reflects open/closed state; `aria-controls` links the button to its panel. `hidden` removes the panel from the accessibility tree when closed. ## How to implement a focus trap in a modal ### 1. Move focus into the modal on open When the modal mounts, call `firstFocusableElement.focus()`. Store the element that triggered the open so you can restore focus on close. Use `useEffect` with the modal's `open` state as the dependency. ### 2. Intercept Tab and Shift-Tab On `keydown`, collect all focusable elements inside the modal (`button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])`). If Tab is pressed on the last one, wrap to the first; Shift-Tab on the first wraps to the last. ### 3. Intercept Escape Close the modal and return focus to the trigger element. Required by WCAG SC 2.1.2 (No Keyboard Trap) — users must always have a keyboard path to exit. ### 4. Mark the backdrop inert Set `inert` on everything outside the modal (`document.body` children except the modal container). The `inert` attribute removes elements from the tab order and accessibility tree without `display:none`. ## Watch out: No ARIA is better than bad ARIA Adding an incorrect role or mismatched `aria-*` state actively breaks screen readers — they announce wrong semantics with no visible signal to sighted users. If you add `role="button"` to a `
`, you must also add `tabindex="0"` and keyboard handlers for Enter and Space. A native `