Auric Artisan · Accessibility

The accessibility checklist

Fifty checks across the whole of WCAG 2.2, each one explained: what it means, who it affects, how to test it, and what the fix looks like in code.

The analyzer decides 11 of these outright and narrows 23 more. The rest needs a person, which is what the explanations are for.

01

Semantics & structure

What a screen reader builds its map from. Nothing below works if this is wrong.

6
  • Landmarks wrap every region of the page PARTLY A 1.3.1 header, nav, main, footer — one main per page, and any repeated nav named.

    What it means

    Landmarks give a screen reader a table of contents for the page: a user can jump straight to the navigation, or to the main content, without listening through everything before it. A page built entirely from div elements offers no such jumps, so every visit starts at the top and walks forward.

    Who it affects

    Screen reader users navigating by region — the fastest way to move around a page they have not seen before.

    How to test it

    In the browser console, run the selector below. You want exactly one main, and every nav that appears more than once to carry its own label.

    What the fix looks like

    document.querySelectorAll('header,nav,main,aside,footer')
    // exactly one <main>; each repeated <nav> needs aria-label

    Usually breaks when

    A component library wraps everything in div, or a layout gains a second main during a refactor and nobody notices.

  • Headings describe structure, not size AUTO A 1.3.1 One h1, no skipped levels. Style with CSS; never pick a heading level to get a font size.

    What it means

    Heading level is an outline, not a type scale. Screen reader users pull up a list of headings to understand and navigate a page, so a jump from h2 to h4 reads as a missing section, and an h3 chosen because it "looked right" puts content in the wrong place in that outline.

    Who it affects

    Screen reader users, who navigate by heading more than by any other means; also anyone using reader mode or a summarising tool.

    How to test it

    List every heading in order and read just the text. It should work as a table of contents on its own.

    What the fix looks like

    [...document.querySelectorAll('h1,h2,h3,h4,h5,h6')]
      .map(h => h.tagName + ' ' + h.textContent.trim())

    Usually breaks when

    A design hands over "small bold label" and the nearest heading level is used to get the size.

  • html lang is set, and switches on foreign passages AUTO A 3.1.1 It picks the speech synthesiser. A French quote inside English needs its own lang.

    What it means

    The lang attribute tells assistive technology which pronunciation rules to use. Without it a screen reader reads English with the user default voice, which can render an English page in a Hindi phonology — technically speech, practically noise.

    Who it affects

    Screen reader users, and anyone using automatic translation or a braille display, where language drives the contraction rules.

    How to test it

    Check the root element, then search the page for passages in another language and confirm each carries its own lang.

    What the fix looks like

    <html lang="en">
      <p>The dish was <span lang="fr">déjà vu</span>.</p>

    Usually breaks when

    A template ships with lang hard-coded to one language and the site later gains a second.

  • Every route has a unique, front-loaded title PARTLY A 2.4.2 In a SPA the title is yours to set on navigation — the browser will not do it.

    What it means

    The title is announced on load and is what a user sees in a long list of tabs or in browser history. Put the distinguishing part first: "Invoices — Acme" beats "Acme — Invoices" when the tab is 90 pixels wide.

    Who it affects

    Everyone with many tabs open; screen reader users, for whom it is the first thing announced on every navigation.

    How to test it

    Navigate the app client-side and watch document.title change. If it does not, nothing is announcing the new page.

    What the fix looks like

    // on route change
    document.title = `${page.name} — Acme`;

    Usually breaks when

    A single-page app changes the view without touching the title, so every route announces the same words.

  • Lists, tables and figures use real elements PARTLY A 1.3.1 A table needs th with scope and a caption. A div grid announces nothing.

    What it means

    Native elements carry relationships that CSS cannot: a list announces how many items it has, a data table lets a user read a cell together with its row and column headers. Rebuild either from div and that structure is gone, leaving a flat run of text.

    Who it affects

    Screen reader users reading tabular data, where losing the header association makes the numbers meaningless.

    How to test it

    Put the cursor in a random data cell with a screen reader running. You should hear the column and row it belongs to.

    What the fix looks like

    <table>
      <caption>Revenue by quarter</caption>
      <tr><th scope="col">Quarter</th><th scope="col">Revenue</th></tr>

    Usually breaks when

    A CSS grid gives the layout the designer wanted, and the semantics are never added back.

  • The reading order matches the visual order BY HAND PRACTICE CSS order, grid placement and absolute positioning all move the eye without moving the DOM.

    What it means

    Assistive technology and keyboard focus follow the DOM. CSS can reorder what the eye sees without touching it, so a sidebar that reads last but appears first will be reached last — the two orders drift apart silently and only ever surface in testing.

    Who it affects

    Keyboard and screen reader users, and anyone whose browser fails to load the stylesheet.

    How to test it

    Disable CSS entirely. The page should still read in a sensible order.

    Usually breaks when

    A responsive layout uses order or grid-area to rearrange columns at one breakpoint and not another.

02

Keyboard & focus

The whole product, without a mouse. This is where most single-page apps fail.

7
  • Every interactive element is reachable and operable by keyboard PARTLY A 2.1.1 A div with onClick is not. Use a button, or add role, tabindex and both key handlers.

    What it means

    A native button is focusable, announces its role, fires on both Enter and Space, and works with voice control — all of it for free. A div with a click handler has none of that, and reproducing it takes four extra attributes people routinely forget one of.

    Who it affects

    Keyboard users, switch and voice control users, and screen reader users, who will never even hear that the element exists.

    How to test it

    Tab through the page and do the primary task without touching the mouse. Anything you can click but cannot reach is a failure.

    What the fix looks like

    // no
    <div onClick={save}>Save</div>
    
    // yes
    <button type="button" onClick={save}>Save</button>

    Usually breaks when

    A designer asks for something that "should not look like a button", and the semantics get removed along with the styling.

  • No keyboard trap — focus can always get back out BY HAND A 2.1.2 Modals, embedded players and custom editors are the usual offenders.

    What it means

    A trap is any place focus can enter and cannot leave with the keyboard alone. It is the most severe failure on this list, because the user is not merely blocked from one feature — they are stuck on the page, with no way forward and no way back.

    Who it affects

    Keyboard-only users. A mouse user closes the dialog and never sees the bug, which is why it survives review.

    How to test it

    Tab into every dialog, picker, editor and embedded frame, then try to Tab and Escape your way out. Do it in both directions with Shift+Tab.

    What the fix looks like

    dialog.addEventListener('keydown', (e) => {
      if (e.key === 'Escape') close();   // and return focus to the trigger
    });

    Usually breaks when

    A focus trap is added to a modal for correctness, and the release path is never wired to Escape or to the close button.

  • Focus is always visible, and never removed without a replacement PARTLY AA 2.4.7 outline: none with nothing in its place is the single most common accessibility bug in the wild.

    What it means

    The focus ring is the keyboard user cursor. Remove it and they are typing blind — they cannot tell which control Enter is about to activate. :focus-visible solves the reason it is usually removed, by showing the ring for keyboard focus and not after a mouse click.

    Who it affects

    Everyone using a keyboard, including sighted power users who never think of themselves as needing accessibility features.

    How to test it

    Tab through every control on the page and watch. If you lose track of where you are, so will everyone else.

    What the fix looks like

    :focus-visible {
      outline: 2px solid currentColor;
      outline-offset: 2px;
    }
    /* never `outline: none` without a replacement */

    Usually breaks when

    A reset stylesheet strips outlines site-wide on day one and nobody puts anything back.

  • Focus order follows meaning PARTLY A 2.4.3 No positive tabindex. tabindex="-1" for programmatic targets, "0" to add to the order.

    What it means

    Tab order comes from the DOM. A positive tabindex jumps the element to the front of the whole page order, which breaks the sequence everywhere else — one tabindex="3" quietly reorders every control on the page around it.

    Who it affects

    Keyboard and screen reader users following a form or a flow in sequence.

    How to test it

    Tab from the top and note the order. It should match the order you would read the page aloud in.

    What the fix looks like

    tabindex="0"   // put it in the natural order
    tabindex="-1"  // focusable by script only
    tabindex="3"   // almost always a mistake

    Usually breaks when

    Someone reaches for tabindex to fix one control and reorders the page.

  • Focus is managed across route changes and dialogs BY HAND PRACTICE On open, move focus in; on close, return it to the trigger. On navigation, move it to the new h1.

    What it means

    A client-side route change replaces the content but leaves focus wherever it was — often on a link that no longer exists, which drops focus to the top of the document with no announcement. The fix is to move focus deliberately and to say what changed.

    Who it affects

    Screen reader and keyboard users, for whom an unannounced navigation is indistinguishable from nothing happening.

    How to test it

    Activate a link with the keyboard, then press Tab. If you are back at the browser chrome, focus was lost.

    What the fix looks like

    // after render
    headingRef.current.focus();   // h1 with tabindex="-1"
    // on dialog close
    triggerRef.current.focus();

    Usually breaks when

    The router animates the transition and focus is forgotten because the page looks like it moved.

  • A skip link comes first and actually moves focus PARTLY A 2.4.1 It must be the first tabbable thing, visible on focus, and target a focusable container.

    What it means

    Without a skip link, every visit to every page starts by tabbing through the whole navigation. The common failure is subtle: the link scrolls the page but focus stays behind, so the next Tab lands back in the menu the user was escaping.

    Who it affects

    Keyboard users on any site with more than a handful of navigation links.

    How to test it

    Load the page, press Tab once, press Enter, then press Tab again. The next stop must be inside the main content.

    What the fix looks like

    <a class="skip" href="#main">Skip to main content</a>
    <main id="main" tabindex="-1">

    Usually breaks when

    The target is a div with no tabindex, so the browser scrolls but does not move focus.

  • Pointer targets are at least 24x24 CSS pixels AUTO AA 2.5.8 New in WCAG 2.2. Spacing counts toward it — small icons need room around them, not just size.

    What it means

    Small targets are hard to hit for anyone with a tremor, on a moving train, or using a stylus. WCAG 2.2 added a 24px floor, and it can be satisfied either by making the target bigger or by giving it enough clear space that a missed tap does not hit its neighbour.

    Who it affects

    Anyone with reduced fine motor control, and every touch user in imperfect conditions.

    How to test it

    Measure the hit area, not the icon. A 16px icon in a 44px button passes; two 16px icons 2px apart do not.

    What the fix looks like

    .icon-btn { min-inline-size: 24px; min-block-size: 24px; }
    /* or leave 24px of clear space between targets */

    Usually breaks when

    A dense toolbar shrinks on mobile, where the targets need to be larger rather than smaller.

03

Forms & errors

Where an inaccessible product stops being annoying and starts costing money.

6
  • Every control has a programmatic label AUTO A 3.3.2 A placeholder is not a label. It vanishes on input and many screen readers skip it.

    What it means

    The label is what a screen reader announces when focus lands on a field, and what voice control users say to reach it. A placeholder disappears the moment someone types, taking the only description of the field with it — which is worst for the people most likely to need it back.

    Who it affects

    Screen reader users, voice control users, and anyone who looks away mid-form and returns to a row of empty boxes.

    How to test it

    Focus each field with a screen reader running. If you hear "edit, blank" and no name, it has no label.

    What the fix looks like

    <label for="email">Email</label>
    <input id="email" type="email" autocomplete="email">
    
    <!-- or, visually hidden -->
    <input aria-label="Search">

    Usually breaks when

    A minimal design removes the labels, and placeholders are pressed into service as a substitute.

  • Errors are identified in text and tied to the field PARTLY A 3.3.1 aria-describedby links the message; aria-invalid marks the state. Colour alone says nothing.

    What it means

    An error has to say three things: that something is wrong, which field it is wrong on, and what to do about it. A red border says the first, vaguely, to people who can see red — the association and the instruction have to be programmatic and in words.

    Who it affects

    Screen reader users, colour-blind users, and anyone under enough pressure to miss a subtle colour change.

    How to test it

    Submit an invalid form with a screen reader on. You should hear the count, then be able to reach each field and hear its message.

    What the fix looks like

    <input id="email" aria-invalid="true" aria-describedby="email-err">
    <p id="email-err">Enter an email address, like [email protected]</p>

    Usually breaks when

    Validation is implemented visually first, and the ARIA wiring is left as a follow-up ticket.

  • Common fields declare autocomplete AUTO AA 1.3.5 name, email, tel, address — it is a requirement, and it halves the typing for everyone.

    What it means

    The autocomplete attribute names what a field collects using a fixed vocabulary, which lets browsers fill it and lets some assistive technology show a familiar icon in place of a word. It is one of the rare criteria where doing the accessible thing measurably lifts conversion.

    Who it affects

    People with motor or cognitive disabilities for whom typing an address is a real cost — and everyone else, who just gets a faster form.

    How to test it

    Check every personal-detail field against the WCAG token list. Watch the browser offer to fill them.

    What the fix looks like

    <input name="tel" type="tel" autocomplete="tel">
    <input name="postcode" autocomplete="postal-code">

    Usually breaks when

    A custom input component drops unrecognised attributes on the way to the DOM.

  • Related controls are grouped with fieldset and legend PARTLY A 1.3.1 Radio sets and checkbox groups are meaningless read one at a time.

    What it means

    A radio button announces its own label but not the question it answers. The legend supplies that question, so "Standard delivery, radio, 1 of 3" becomes "Delivery method: standard delivery, radio, 1 of 3".

    Who it affects

    Screen reader users, who otherwise hear the options without ever hearing what is being asked.

    How to test it

    Focus a radio in a group. If you cannot tell what the group is for, it needs a legend.

    What the fix looks like

    <fieldset>
      <legend>Delivery method</legend>
      <label><input type="radio" name="ship"> Standard</label>
    </fieldset>

    Usually breaks when

    fieldset is hard to style, so it gets replaced by a div and a heading.

  • Validation is announced, not just painted BY HAND PRACTICE A live region or a focus move — otherwise a screen reader user submits into silence.

    What it means

    Client-side validation usually re-renders the form in place. Nothing about that re-render is announced, so a screen reader user presses Submit, hears nothing, and concludes the button is broken.

    Who it affects

    Screen reader users. This is the difference between a form that is hard and a form that is impossible.

    How to test it

    Submit an invalid form with the screen reader on and your eyes closed. If you learn nothing, neither do they.

    What the fix looks like

    <div role="alert">2 problems stop this form being submitted</div>
    // or move focus to the summary, then to the first bad field

    Usually breaks when

    The error summary is rendered before it exists in the DOM, so the live region has nothing to announce.

  • Legal and financial submissions are reversible or confirmed BY HAND A 3.3.4 Reversible, checked, or confirmed. Pick one before taking the payment.

    What it means

    For anything that costs money, forms a contract, or deletes data, WCAG asks for one of three protections: the action can be undone, the input is checked and correctable, or the user gets a confirmation step before it is final.

    Who it affects

    People with cognitive disabilities and anyone who mis-taps — which, given enough traffic, is everyone.

    How to test it

    Walk the checkout. Is there a review step, an undo window, or validation that catches the mistake before the charge?

    Usually breaks when

    A one-click flow is added for speed and the confirmation step is what gets cut.

04

Images & media

Alt text is a decision, not a field to fill in.

6
  • Every image is either described or explicitly decorative PARTLY A 1.1.1 alt="" and aria-hidden for decoration. A filename in the alt is worse than nothing.

    What it means

    Alt text answers one question: what would a sighted person get from this image that they would not get from the surrounding text? If the answer is nothing, alt="" is the correct, deliberate value — an empty alt is a decision, a missing alt attribute is an oversight, and screen readers treat them differently.

    Who it affects

    Screen reader users; also anyone on a slow connection where images do not load.

    How to test it

    Turn images off. Anything you now cannot understand needed better alt text; anything now cluttered with filenames needed alt="".

    What the fix looks like

    <img src="q3.png" alt="Revenue fell 12% in Q3, the first drop since 2019">
    <img src="flourish.svg" alt="">

    Usually breaks when

    A CMS makes alt a required field, so authors type the filename to get past it.

  • Text in an image is repeated in real text BY HAND A 1.4.5 It cannot be resized, restyled, translated or searched — screenshots of code are the worst case.

    What it means

    Text baked into a picture is invisible to search, translation, reader modes and text-spacing overrides, and it blurs when someone zooms. A code screenshot in documentation cannot be copied, read aloud, or searched for the error message it shows.

    Who it affects

    Low-vision users who zoom or restyle text, and anyone reading in a second language.

    How to test it

    Try to select the text with the mouse. If you cannot, it is a picture.

    Usually breaks when

    A designed quote or a code sample is exported as PNG because it was quicker than building it.

  • Video has captions; audio has a transcript BY HAND A 1.2.2 Auto-captions are a draft. Someone reads them before they ship.

    What it means

    Captions carry dialogue and the sounds that matter; a transcript makes the same content searchable and skimmable. Machine captions get names, jargon and figures wrong at exactly the moments those things matter most, so they are a starting point, not a deliverable.

    Who it affects

    Deaf and hard-of-hearing users; and the far larger group watching with the sound off in public.

    How to test it

    Watch with the sound off. Then read the caption track for the words a machine would not know.

    Usually breaks when

    Auto-captions are enabled once, and nobody reads them again.

  • Video has audio description where it carries visual information BY HAND AA 1.2.5 If the picture says something the soundtrack does not, it needs describing.

    What it means

    Audio description narrates what is shown but not said — a diagram, an on-screen figure, a demonstration performed in silence. Where a video is planned well, the narration covers this and no separate track is needed.

    Who it affects

    Blind and low-vision users watching a video whose meaning is partly visual.

    How to test it

    Listen without watching. Note every moment you lose the thread.

    Usually breaks when

    A product demo shows the interface while the voiceover says "as you can see here".

  • Nothing plays automatically for more than three seconds PARTLY A 1.4.2 Or there is a control to stop it, reachable before everything else.

    What it means

    Autoplaying audio competes with a screen reader speaking, and the two cannot be separated by ear. The stop control has to come early in the focus order, because reaching it is exactly what the noise makes difficult.

    Who it affects

    Screen reader users above all, then everyone in an office or a library.

    How to test it

    Load the page with a screen reader running. If you cannot hear it, that is the failure.

    Usually breaks when

    A background video ships muted, and a later change adds a soundtrack.

  • Icons carry an accessible name or are hidden PARTLY PRACTICE An icon-only button needs aria-label; an icon beside a label needs aria-hidden.

    What it means

    Two failures with one cause. An icon-only button with no name announces as "button" and nothing else. An icon left visible to assistive technology beside a text label gets announced twice, or announced as its filename.

    Who it affects

    Screen reader users, and voice control users who need a name to say out loud.

    How to test it

    Read the accessibility tree. Every button should have a name; no name should be doubled.

    What the fix looks like

    <button aria-label="Delete"><svg aria-hidden="true">…</svg></button>
    <button><svg aria-hidden="true">…</svg> Delete</button>

    Usually breaks when

    An icon component is used both ways and hard-codes one of them.

05

Colour & contrast

The part an automated tool can genuinely finish for you.

6
  • Body text reaches 4.5:1, large text 3:1 AUTO AA 1.4.3 Against the composited background — the colour after every translucent layer, not the token.

    What it means

    Contrast is measured between the text and what is actually behind it. Translucent overlays, tinted cards and gradients all change that background, so the number in the design token is frequently not the number on screen — a colour can pass on white and fail on the card it lives in.

    Who it affects

    Low-vision users, everyone over about forty, and anyone outdoors in sunlight.

    How to test it

    Sample the rendered pixels, not the CSS values. Composite every translucent layer before comparing.

    What the fix looks like

    /* 4.5:1 body, 3:1 for 24px or 18.5px bold */

    Usually breaks when

    A grey is chosen against white, then reused on a cream surface where it no longer passes.

  • Controls and meaningful graphics reach 3:1 AUTO AA 1.4.11 Input borders, toggles, chart series, focus rings.

    What it means

    Text is not the only thing that has to be seen. The boundary that tells you where an input begins, the toggle that shows a setting is on, the line in a chart — all need 3:1 against what surrounds them. Hairline borders are the usual casualty of this rule.

    Who it affects

    Low-vision users, who otherwise cannot find the edges of the controls.

    How to test it

    Measure the control boundary against the page, and the parts of a graphic that carry meaning against their surroundings.

    Usually breaks when

    A very light border is chosen for elegance, at about 1.2:1.

  • Colour is never the only carrier of meaning PARTLY A 1.4.1 Links underlined, errors iconned, chart series labelled or patterned.

    What it means

    If removing all colour removes the information, colour was doing the work alone. The fix is a second cue that survives greyscale: an underline, an icon, a shape, a direct label, a pattern.

    Who it affects

    About one in twelve men and one in two hundred women, plus anyone printing in mono.

    How to test it

    Screenshot it and desaturate. Anything you can no longer tell apart is a failure.

    Usually breaks when

    A status system is built on red, amber and green, and the shapes are added only for the icons.

  • Hover, focus, active and selected states all pass too PARTLY AA 1.4.11 A rule that wins at rest can lose on hover — measure the state, not the resting colour.

    What it means

    Contrast is usually checked once, in the resting state. Every other state is a different pair of colours, and a hover that swaps a background without swapping the text is the classic way a passing button becomes unreadable at the moment it is being used.

    Who it affects

    Low-vision and keyboard users — the focus state is the one a keyboard user sees most.

    How to test it

    Measure each state separately: rest, hover, focus, active, selected, disabled.

    Usually breaks when

    A hover fills the button with the brand colour and the label stays white.

  • Dark mode and forced-colors are checked separately PARTLY PRACTICE An inverted palette almost never passes, and Windows High Contrast drops your backgrounds entirely.

    What it means

    Dark mode is a second palette, not a filter, and needs its own measurements. Forced-colors mode goes further: the operating system replaces your colours wholesale, so anything carried by a background image, a shadow or a gradient simply disappears.

    Who it affects

    Low-vision users, who are the main users of forced-colors mode.

    How to test it

    Run the full contrast pass again in dark mode. Then turn on Windows High Contrast and look for what vanished.

    What the fix looks like

    @media (forced-colors: active) {
      .card { border: 1px solid CanvasText; }
    }

    Usually breaks when

    A focus ring is drawn with box-shadow, which forced-colors mode removes.

  • Simulated for protanopia, deuteranopia and tritanopia AUTO PRACTICE On real screens, not only on the swatch sheet.

    What it means

    Simulation shows what a palette becomes for the three common types of colour-vision deficiency. Palettes that look distinct on a swatch sheet often collapse into two colours once they are small, adjacent and rendered as chart series.

    Who it affects

    Roughly one in twelve men.

    How to test it

    Simulate each type on real screens — the chart, the status chips, the map — not on the colour ramp in isolation.

    Usually breaks when

    The palette is tested and the interface built from it is not.

06

Motion & preferences

The settings the operating system already knows about.

4
  • prefers-reduced-motion is honoured PARTLY AAA 2.3.3 Not only decorative animation — parallax, autoplaying carousels and scroll-jacking.

    What it means

    Vestibular disorders make large motion genuinely sickening: parallax, zooming transitions and scroll-jacking can cause nausea and migraine that outlast the visit. The operating system already knows the preference; honouring it costs one media query.

    Who it affects

    People with vestibular disorders and migraine — and anyone who finds heavy motion tiring.

    How to test it

    Turn on Reduce Motion in the OS and reload. Large movement should be gone, not merely faster.

    What the fix looks like

    @media (prefers-reduced-motion: reduce) {
      *, *::before, *::after {
        animation-duration: .01ms !important;
        transition-duration: .01ms !important;
      }
    }

    Usually breaks when

    The query covers CSS transitions, and a JavaScript scroll animation is left running.

  • Nothing flashes more than three times a second BY HAND A 2.3.1 This one can cause seizures. It is not a preference.

    What it means

    Flashing between about 3 and 55 times a second can trigger a seizure in people with photosensitive epilepsy. This is the one item on the list where the consequence of getting it wrong is a medical emergency rather than an inconvenience.

    Who it affects

    People with photosensitive epilepsy.

    How to test it

    Look for rapid flashing or fast red transitions in video, animation and loading states.

    Usually breaks when

    A glitch effect or a strobing loader is added for style.

  • Moving or auto-updating content can be paused PARTLY A 2.2.2 Carousels, tickers, live feeds — pause, stop or hide.

    What it means

    Anything that moves, blinks or updates by itself for more than five seconds needs a way to stop it. Motion beside text pulls attention away mid-sentence, and content that rewrites itself while being read is impossible to follow.

    Who it affects

    People with attention-related and cognitive disabilities; screen reader users, when the content changes as it is read.

    How to test it

    Find every moving thing on the page and look for its pause control.

    Usually breaks when

    A hero carousel advances on a timer with no controls at all.

  • Timeouts are announced, or there are none BY HAND AAA 2.2.6 A session that dies silently loses the form someone spent twenty minutes on.

    What it means

    Where inactivity can destroy data, the user has to be warned how long they have — before they start, not after it has gone. People who take longer to read or type are exactly the people timeouts hit.

    Who it affects

    People with cognitive or motor disabilities, who may need several times the assumed duration.

    How to test it

    Leave a long form idle past the timeout. Was there a warning, and did the data survive?

    Usually breaks when

    A session length is set for security and the front end never learns about it.

07

ARIA & custom widgets

No ARIA beats bad ARIA. Reach for a native element first, every time.

5
  • Custom widgets expose name, role and value PARTLY A 4.1.2 And keep them in sync as state changes. A silent aria-expanded is a broken disclosure.

    What it means

    Every control has to tell assistive technology three things: what it is called, what kind of thing it is, and what state it is in. The hard part is the third — the state has to be updated every time it changes, and a stale aria-expanded is worse than none, because it actively lies.

    Who it affects

    Screen reader and voice control users operating anything that is not a plain link or button.

    How to test it

    Open the accessibility tree in devtools, operate the widget, and watch the state change with it.

    What the fix looks like

    <button aria-expanded="false" aria-controls="menu">Filters</button>
    // on toggle: btn.setAttribute('aria-expanded', String(open))

    Usually breaks when

    The attribute is set in the initial render and never touched again.

  • The widget follows its APG keyboard pattern BY HAND PRACTICE Tabs, menus, comboboxes and trees each have an expected key map. Users know it already.

    What it means

    A tab list responds to arrow keys, Home and End; a menu closes on Escape and returns focus to its trigger. These conventions are documented in the ARIA Authoring Practices Guide, and users of assistive technology have learned them — inventing your own means they have to learn yours.

    Who it affects

    Screen reader and keyboard users who already know how these components behave everywhere else.

    How to test it

    Open the APG page for the pattern and work through its key map on your implementation.

    Usually breaks when

    A component gets the roles right and the keyboard handling never gets written.

  • Status messages reach a live region PARTLY AA 4.1.3 polite for confirmations, assertive only for genuine interruptions. Never both.

    What it means

    A message that appears without taking focus is invisible to a screen reader unless it lands inside a live region. The container has to exist in the DOM before the text arrives, or there is nothing there to observe the change.

    Who it affects

    Screen reader users, who otherwise never learn that anything saved, failed or finished.

    How to test it

    Trigger every toast, saved-state and result count with a screen reader running.

    What the fix looks like

    <!-- render the container empty, then fill it -->
    <div role="status" aria-live="polite">Saved</div>

    Usually breaks when

    The whole toast including its live region is inserted at once, so nothing is announced.

  • No aria-hidden on a focusable element AUTO PRACTICE It creates a control a screen reader cannot see but can still land on.

    What it means

    aria-hidden removes an element from the accessibility tree without removing it from the focus order. Focus can land somewhere that, as far as the screen reader is concerned, does not exist — so the user hears nothing at all and cannot tell what happened.

    Who it affects

    Screen reader users, for whom this presents as focus vanishing.

    How to test it

    Search for aria-hidden="true" and check nothing inside it is focusable.

    What the fix looks like

    // hide from everyone, not just from AT
    el.hidden = true;   // or inert on the container

    Usually breaks when

    A modal hides the background with aria-hidden and leaves the links behind it tabbable.

  • Link text makes sense out of context PARTLY A 2.4.4 "Read more" repeated eleven times is a list of eleven identical links.

    What it means

    Screen reader users often pull up a list of every link on the page to navigate. In that list there is no surrounding paragraph, so eleven "read more" links become eleven identical entries with no way to tell them apart.

    Who it affects

    Screen reader users navigating by link; also anyone scanning rather than reading.

    How to test it

    Extract every link text on the page and read the list. Any duplicate that leads somewhere different is a failure.

    What the fix looks like

    <a href="/q3">Read the Q3 report</a>
    <!-- or keep the visible text and extend it -->
    <a href="/q3">Read more<span class="visually-hidden"> about Q3</span></a>

    Usually breaks when

    A card component takes a fixed call to action and is reused down a whole index page.

08

Zoom, reflow & spacing

The same page in the conditions people actually browse it.

5
  • No horizontal scroll at 320 CSS pixels wide AUTO AA 1.4.10 Equivalent to 400% zoom on a desktop. Two-dimensional scrolling is the failure.

    What it means

    At 400% zoom a 1280px desktop viewport becomes 320px of usable width. Content must reflow into that single column: having to scroll sideways to read every line of every paragraph is what the criterion exists to prevent.

    Who it affects

    Low-vision users who browse zoomed in permanently, and every small-phone user.

    How to test it

    Set the viewport to 320px, or zoom to 400%. Nothing may require sideways scrolling except data tables and code.

    Usually breaks when

    A fixed-width element — a table, an embed, a long unbroken string — sets a floor the layout cannot go below.

  • Text resizes to 200% without loss PARTLY AA 1.4.4 Fixed-height containers with overflow: hidden are where the text goes.

    What it means

    Text must double in size without content being cut off or made unusable. The usual failure is a container with a fixed height: the text grows, the box does not, and the last line is clipped or hidden entirely.

    Who it affects

    Low-vision users, and anyone whose browser has a larger default font size than yours.

    How to test it

    Zoom text only to 200%. Look for clipped lines, overlaps and buttons whose labels no longer fit.

    What the fix looks like

    /* let it grow */
    .card { min-block-size: 120px; }   /* not height: 120px */

    Usually breaks when

    A card grid is given a fixed height so the rows line up.

  • User text-spacing overrides do not break the layout PARTLY AA 1.4.12 Line height 1.5, paragraph spacing 2em, letter 0.12em, word 0.16em — nothing may be clipped.

    What it means

    Some users apply a stylesheet that opens up spacing to make text readable, particularly people with dyslexia. WCAG names exact values that must survive without losing content — which mostly means not fixing the height of anything that holds text.

    Who it affects

    Dyslexic and low-vision users who override spacing to read comfortably.

    How to test it

    Apply the four values as a user stylesheet and look for clipping and overlap.

    What the fix looks like

    * { line-height: 1.5 !important;
        letter-spacing: .12em !important;
        word-spacing: .16em !important; }
    p { margin-block-end: 2em !important; }

    Usually breaks when

    A component sets an exact height to match a design, and a fifth word arrives.

  • Both orientations work PARTLY AA 1.3.4 No locking to portrait unless the content genuinely requires it.

    What it means

    Many users have a device fixed in one orientation — mounted on a wheelchair, on a stand, on an arm. Locking the interface to the other one makes it unusable rather than merely awkward.

    Who it affects

    People whose device is physically mounted, and anyone with a keyboard case.

    How to test it

    Rotate the device. The layout should adapt rather than refuse.

    Usually breaks when

    A mobile app-style layout assumes portrait and locks it in the manifest.

  • Focused elements are not hidden behind sticky chrome BY HAND AA 2.4.11 New in 2.2, and a sticky header plus scroll-into-view is exactly how it happens.

    What it means

    Tabbing forward scrolls the next control into view, and a sticky header can sit exactly where it lands — so the element has focus, is scrolled to, and is completely covered. Keyboard users are left typing into something they cannot see.

    Who it affects

    Keyboard users on any page with a sticky header or a fixed toolbar.

    How to test it

    Tab down a long page with sticky chrome and watch for the focused control disappearing underneath it.

    What the fix looks like

    /* keep the focused element clear of the sticky header */
    :target, :focus-visible { scroll-margin-block-start: 6rem; }

    Usually breaks when

    A sticky header is added late, long after the focus order was tested.

09

Testing & proof

How it stays fixed after the sprint that fixed it.

5
  • An automated pass runs in CI on every pull request AUTO PRACTICE It catches about a third. That third should never reach a human reviewer again.

    What it means

    Automated rules reliably find missing alt attributes, unlabelled fields, contrast failures and broken ARIA references. That is roughly a third of what matters, and it is the third that is tedious to find by hand — so it belongs in CI, freeing review for the two thirds that need judgement.

    Who it affects

    Everyone downstream, including the reviewers who stop re-finding the same class of bug.

    How to test it

    Open a pull request that introduces an unlabelled input. The build should fail.

    What the fix looks like

    # fail the build, do not just report
    npx axe-ci $URL --exit-code

    Usually breaks when

    The job is added as advisory and stays permanently amber.

  • A keyboard-only pass is part of review BY HAND PRACTICE Unplug the mouse and do the primary task. Ten minutes, most of the remaining bugs.

    What it means

    The cheapest high-yield test there is. Most focus traps, invisible focus rings, unreachable controls and broken dialogs surface within a few minutes of trying to complete one real task without a pointer.

    Who it affects

    Keyboard, switch and voice users — and it doubles as a check on your focus management.

    How to test it

    Put the mouse out of reach and complete the main flow. Note every point you get stuck.

    Usually breaks when

    It is nobody specific job, so it happens the week before an audit.

  • A screen reader pass on one real combination BY HAND PRACTICE NVDA with Firefox, or VoiceOver with Safari. Not the browser plugin that reads the DOM.

    What it means

    Screen readers differ from each other and from what the accessibility tree suggests they will do. Testing one real pairing end to end teaches more than reading three specifications, and it is the only way to hear what your live regions actually announce.

    Who it affects

    Screen reader users. Everything else on this list is inference; this is observation.

    How to test it

    Pick one pairing and complete the primary task with the screen reader on and the monitor off.

    Usually breaks when

    The team tests with a browser extension that reports the tree rather than speaking it.

  • Findings carry evidence and a criterion BY HAND PRACTICE A screenshot and a number turn "this feels wrong" into something a developer can close.

    What it means

    A finding without evidence gets argued about; a finding with a marked-up screenshot and a criterion number gets fixed. It also gives you an audit trail with a date on it, which is what a procurement question actually asks for.

    Who it affects

    The developer who has to reproduce it, and whoever answers the compliance questionnaire.

    How to test it

    Read your last three findings. Could someone reproduce each one without asking you a question?

    Usually breaks when

    Findings are recorded in a spreadsheet with a column for the page and none for the proof.

  • People with disabilities are in the testing loop BY HAND PRACTICE Every checklist including this one is a proxy for that, never a replacement.

    What it means

    A checklist encodes what previous failures taught someone else. It cannot tell you that a flow is technically conformant and still exhausting, or that a pattern is correct and unfamiliar. Only the people who use this software with assistive technology every day can.

    Who it affects

    Everyone — this is the check that finds what the other forty-six were a proxy for.

    How to test it

    Ask when a disabled user last tried the flow you are shipping. If the answer is never, that is the finding.

    Usually breaks when

    Conformance is treated as the goal rather than as the floor.