Web accessibility
Accessibility here means two walks that every flow must survive: keyboard only, where every flow completes without a mouse, and a screen reader, where every control announces a name, a role and its state. The method and most rules come from Jakub Krehel's better-accessibility skill (jakubkrehel/skills), with accessibility facts from his better-interface, interface-review and break skills, Emil Kowalski's prototype, feel (diip3sh), transitions.dev and fluid-functionalism. Each fact is re-checked against the W3C pages it names: WCAG 2.2, its Understanding documents and the ARIA Authoring Practices Guide (APG). Where a source disagreed with those pages, the W3C form is written and the source's version is named once.
When unsure, take the platform default over a custom rebuild, and remove ARIA rather than add it. SVG names and roles are in svg ACCESSIBILITY.md; <dialog>, popover and inert mechanics are in modern-css POPOVER-AND-DIALOG.md; animation recipes under reduced motion are in ui-animation PERFORMANCE.md. The owner's product (app-ui) records four deliberate departures from this skill's defaults; each file names the one that touches it as an "app-ui does X instead" line.
Apply only the routes and checks for the selected job. Read a linked skill's named file from its installed folder when available, or from the linked source. Report an unavailable required reference instead of inventing it. [skill-authoring, DISTRIBUTION.md#composing-skills]
| The job | Read |
|---|---|
Focus rings, forced colors, focus not obscured, skip links, tabindex, roving tabindex, aria-activedescendant, dialogs, trapping and restoring focus, the APG key maps, Escape order, SPA route changes |
FOCUS-AND-KEYBOARD.md |
| The four rules of ARIA, button vs link vs div, landmarks, breadcrumbs, headings, page titles, page and passage language (3.1.1, 3.1.2), accessible names, Label in Name, common ARIA mistakes, disabled states, switch and tab states | SEMANTICS-AND-ARIA.md |
Labels, required fields, the error pattern, autocomplete tokens, type and inputmode, paste, redundant entry (3.3.7), accessible authentication (3.3.8), submit while pending |
FORMS.md |
Phone fields: type="tel", storing E.164 with its +, validating with libphonenumber, Israeli number ranges, the prefix is not the carrier |
PHONE-NUMBERS.md |
.sr-only, choosing how to announce a change, live regions, loading announcements, aria-hidden, alt text by purpose, video and audio |
SCREEN-READERS.md |
| Reduced motion, autoplay and Pause, Stop, Hide, toasts and timing, content on hover or focus, text resize, reflow, viewport zoom, contrast | MOTION-AND-ZOOM.md |
| Target size (2.5.8 and 2.5.5), the spacing exception, expanding hit areas, collisions, decorative layers, dragging and gesture alternatives | HIT-AREAS.md |
| Running a review: the two walks, criteria vs conventions, severity on P0 to P3, escalation triggers, verification, the report table, removed signals in a diff, the grep table | REVIEW.md |
The rules
- Native elements first.
<button>for actions,<a href>for navigation, never<div onClick>; a real link supports Cmd/Ctrl/middle-click. The first rule of ARIA, in the W3C wording: "If you can use a native HTML element or attribute with the semantics and behavior you require already built in ... then do so" (SEMANTICS-AND-ARIA.md). [Jakub Krehel, better-accessibility] - Focus is always visible. Style
:focus-visible, prefer the browser's unmodified ring, and give a custom ring 3:1 against every adjacent color it crosses (1.4.11). A focused element is never entirely hidden by sticky chrome (2.4.11) (FOCUS-AND-KEYBOARD.md). [Jakub Krehel, better-accessibility] - Every pointer path has a keyboard path, following the APG pattern for the widget: Escape closes overlays, arrows move inside composite widgets, Tab moves between widgets, Enter and Space activate. Only
tabindex="0"and-1, never positive (FOCUS-AND-KEYBOARD.md). [Jakub Krehel, better-accessibility] - Move and restore focus on purpose: into a dialog on open, back to the trigger on close, to the new view's
<h1>or<main>on a client-side route change (FOCUS-AND-KEYBOARD.md). [Jakub Krehel, better-accessibility] - Every control has a name, and the visible label is in it (2.5.3). Icon-only buttons get
aria-label; decorative parts getaria-hidden="true", never on or above anything focusable (SEMANTICS-AND-ARIA.md). [Jakub Krehel, better-accessibility] - Every input is labelled and typed, with a valid
autocompletetoken on fields about the user (1.3.5); a placeholder is never the label; paste is never blocked; validate on submit and pointaria-describedbyat the inline error (FORMS.md). [Jakub Krehel, better-accessibility] - Announce with the lightest mechanism that works: a focus move, then
aria-describedby, thenrole="status", thenrole="alert"for urgent untied errors only (SCREEN-READERS.md). [Jakub Krehel, better-accessibility] - Meaning never rides on color alone (1.4.1); text meets 4.5:1 and large text 3:1 (1.4.3); component boundaries, states and focus indicators meet 3:1 (1.4.11) (MOTION-AND-ZOOM.md). [Jakub Krehel, better-accessibility]
- Motion respects the user, and nothing the user needs runs on a timer. Honor
prefers-reduced-motion; anything that moves, blinks or scrolls on its own for more than 5 seconds has a mechanism to pause, stop or hide it (2.2.2, Level A) (MOTION-AND-ZOOM.md). [Jakub Krehel, better-accessibility] and WCAG 2.2.2 - Survive zoom: 200% text resize (1.4.4), reflow at 320 CSS px (1.4.10), never
maximum-scale=1oruser-scalable=no(MOTION-AND-ZOOM.md). [Jakub Krehel, better-accessibility] - Targets are at least 24 by 24 CSS px or meet an exception (2.5.8, AA), and every drag has a single-pointer alternative (2.5.7) (HIT-AREAS.md). [Jakub Krehel, better-accessibility]
- A finding cites a WCAG 2.2 A or AA criterion, or a task a user of assistive technology cannot complete. AAA criteria, APG conventions and the 44px and 40px targets are recommendations, ranked P2 or P3 at most (REVIEW.md). [Jakub Krehel, better-accessibility]
Prove it
Tab through every flow with the mouse unplugged and see a focus indicator at every stop, in an order that matches the screen; read each control's computed name, role and state in the browser's accessibility tree; open and close every overlay and watch focus go in and come back; submit every form empty and land on the first invalid field with its error announced; switch on reduced motion and see no slide, zoom or parallax; zoom to 200% and narrow to 320 CSS px with nothing clipped and no sideways scroll; measure every text and state pair with a contrast tool; run an automated audit; and every file's own Prove it checks pass for the parts you touched.