Accessibility cheat sheet
A scannable Accessibility reference: 18 short snippets across 10 topics, each linking back to the lesson it came from.
At a glance
| Topic | What it covers | |
|---|---|---|
| Semantic HTML and keyboard access | A native element arrives with behaviour, keyboard support, focus handling and a role that assistive technology | lesson |
| Focus management, ARIA and testing | When content appears or disappears without a page load, keyboard users can lose their place entirely. Focus rules for | lesson |
| How people use assistive technology | A screen reader reads the accessibility tree, not the pixels. It has two modes: a browse mode that moves element by | lesson |
| Accessible components: modals, menus, tabs and comboboxes | Dialog focus trapping and returning focus, disclosure and menu patterns, tab and accordion semantics, combobox keyboard | lesson |
| Live regions and dynamic content announcements | The rule that ties all of this together: after any change, the user must be able to tell what happened and where they | lesson |
| Data tables, charts and data visualisation | A scrollable table container must be focusable, or a keyboard user cannot scroll it. A wrapper with tabindex="0" and an | lesson |
| Motion, zoom, reflow and vestibular safety | Reduced motion, pausing and stopping animation, 400 percent zoom and reflow, text spacing, orientation, and target size | lesson |
| Cognitive accessibility: plain language and error recovery | Redundant entry and memory load are the criteria people most often skip, and they are among the cheapest to fix. Every | lesson |
| Accessibility in design systems and SPA frameworks | A design system is where accessibility can be solved once. If the button component renders a real button and requires | lesson |
| Auditing, remediation and compliance reporting | Choosing automated scanners, structuring a manual audit, prioritising fixes by user impact, VPAT and EN 301 549 | lesson |
Quick snippets
Semantic HTML and keyboard access
Keyboard access and tab order
<!-- avoid: positive tabindex reorders the whole page -->
<input tabindex="1">
<!-- include something that is not focusable by default -->
<div tabindex="0" role="region" aria-label="Terminal output">...</div>
<!-- keep it in the tree, but not in the tab order -->
<button tabindex="-1">Close</button>
<!-- focus target for skip links needs tabindex="-1" to accept focus -->
<h2 id="section-2" tabindex="-1">Section 2</h2>
Keyboard access and tab order
/* a focus ring that survives on both light and dark backgrounds */
:focus-visible {
outline: 3px solid #1a5fff;
outline-offset: 2px;
border-radius: 2px;
}
/* never remove the indicator without a replacement */
/* button:focus { outline: none; } <- do not do this */Full lesson: Semantic HTML and keyboard access →
Focus management, ARIA and testing
ARIA, when it is actually needed
<!-- good: native toggle with correct open state -->
<button type="button" aria-expanded="false" aria-controls="menu">Menu</button>
<ul id="menu" hidden>
<li><a href="/orders">Orders</a></li>
</ul>
<!-- icon-only control needs an accessible name -->
<button type="button" aria-label="Close dialog">
<svg aria-hidden="true" focusable="false" viewBox="0 0 16 16">...</svg>
</button>
A testing routine that finds real problems
# automated pass in CI, fails the build on new violations
npx axe https://localhost:3000 --exit
# or in a Playwright test
npx playwright test a11y.spec.jsFull lesson: Focus management, ARIA and testing →
How people use assistive technology
Screen readers are not one behaviour
<!-- what a screen reader announces depends entirely on this markup -->
<nav aria-label="Breadcrumb">
<ol>
<li><a href="/guides">Guides</a></li>
<li><a href="/guides/dns" aria-current="page">DNS</a></li>
</ol>
</nav>
<!-- without a label, this is announced as an unnamed navigation region -->
<nav>...</nav>
Magnification, voice, switches and captions
<!-- voice control matches the visible text - keep them identical -->
<button type="submit">Save changes</button>
<!-- this button cannot be activated by voice, because the label is a picture -->
<button type="submit" aria-label="Save changes"><svg aria-hidden="true">...</svg></button>
<!-- captions need a real track, not an auto-generated guess -->
<video controls>
<source src="/talk.mp4" type="video/mp4" />
<track kind="captions" src="/talk.en.vtt" srclang="en" label="English" default />
<track kind="descriptions" src="/talk.descriptions.en.vtt" srclang="en" label="Audio description" />
</video>
Testing with real users
When there is no budget for a study
install a screen reader and complete your primary task with the screen off
complete the same task using only the keyboard
zoom the browser to 200 percent and then 400 percent
turn on the operating system's reduced motion setting
use voice control for one form on your phone
Five sessions of an hour each find more than any scanner.Full lesson: How people use assistive technology →
Accessible components: modals, menus, tabs and comboboxes
Modals and dialogs
<dialog id="confirm" aria-labelledby="confirm-title" aria-describedby="confirm-body">
<h2 id="confirm-title">Delete this project?</h2>
<p id="confirm-body">This removes the project and everything in it. This cannot be undone.</p>
<button type="button" data-action="cancel">Cancel</button>
<button type="button" data-action="delete">Delete project</button>
</dialog>
Comboboxes and libraries
<label for="country">Country</label>
<input id="country" role="combobox" aria-expanded="false" aria-controls="country-list"
aria-autocomplete="list" autocomplete="country-name" />
<ul id="country-list" role="listbox" aria-label="Countries" hidden>
<li role="option" id="opt-nl" aria-selected="false">Netherlands</li>
<li role="option" id="opt-no" aria-selected="false">Norway</li>
</ul>
<!-- and the live region that announces how many results match -->
<p role="status" class="sr-only">2 results available</p>Full lesson: Accessible components: modals, menus, tabs and comboboxes →
Live regions and dynamic content announcements
Live regions that work
<!-- polite: announced when the user is idle. Use for most updates. -->
<p role="status" aria-live="polite">2 results available</p>
<!-- assertive: interrupts immediately. Use only for errors that need action. -->
<div role="alert">Payment failed. Check your card details.</div>
<!-- a region must be in the DOM before the content is added -->
<div id="cart-status" role="status" aria-live="polite" aria-atomic="true"></div>
<!-- and a busy indicator that is announced once -->
<button type="button" aria-busy="true">Saving...</button>
Async results and validation
// forcing a repeat announcement of the same string
function announce(el, message) {
el.textContent = ""; // must reach the DOM before the new text
requestAnimationFrame(() => { el.textContent = message; });
}Full lesson: Live regions and dynamic content announcements →
Data tables, charts and data visualisation
Table structure
<!-- a sortable column header, with the state exposed -->
<th scope="col" aria-sort="ascending">
<button type="button">
Revenue
<span aria-hidden="true">▲</span>
</button>
</th>
<!-- and a table that is scrollable on small screens needs a keyboard-reachable wrapper -->
<div role="region" aria-labelledby="rev-caption" tabindex="0">
<table aria-labelledby="rev-caption">...</table>
</div>
Dashboards and refreshing data
<section aria-labelledby="panel-orders">
<h2 id="panel-orders">Orders today</h2>
<p class="value">1,284 <span class="delta">up 12 percent on yesterday</span></p>
<p role="status" aria-live="polite" class="sr-only" id="orders-live"></p>
</section>Full lesson: Data tables, charts and data visualisation →
Motion, zoom, reflow and vestibular safety
Motion and vestibular safety
// read the preference in script when behaviour depends on it
const prefersReducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
function scrollToSection(el) {
el.scrollIntoView({
behavior: prefersReducedMotion ? "auto" : "smooth",
block: "start"
});
}Full lesson: Motion, zoom, reflow and vestibular safety →
Cognitive accessibility: plain language and error recovery
Forgiving error recovery
<!-- a confirmation that names the thing, and an undo afterwards -->
<dialog id="delete-project" aria-labelledby="del-title">
<h2 id="del-title">Delete "Renewal pipeline"?</h2>
<p>The project and its 42 tasks will be removed. You can restore it for 30 days.</p>
<button type="button">Cancel</button>
<button type="button">Delete project</button>
</dialog>
<div role="status" class="toast">
Project deleted.
<button type="button">Undo</button>
</div>Full lesson: Cognitive accessibility: plain language and error recovery →
Accessibility in design systems and SPA frameworks
Preventing regressions
# a CI job that fails a pull request on an accessibility regression
- name: Component accessibility tests
run: npm run test -- --coverage=false a11y
- name: Scan built pages
run: npx axe ./dist --exit
continue-on-error: falseFull lesson: Accessibility in design systems and SPA frameworks →
Auditing, remediation and compliance reporting
Structuring an audit
# a scanner in CI, scoped to the pages that matter
npx pa11y-ci --config .pa11yci.json
# a single page, with a full report
npx pa11y https://example.com/checkout --reporter json > checkout-a11y.json
# and one run in the browser against the rendered DOM
# axe DevTools or the axe-core CLI, which sees client-rendered markup
Conformance reporting and CI
# a pragmatic CI gate: only new violations fail the build
- name: Accessibility scan
run: npx pa11y-ci --config .pa11yci.json
- name: Fail on new violations
run: node tools/a11y-diff.js --baseline .a11y-baseline.json --fail-on newFull lesson: Auditing, remediation and compliance reporting →
FAQ
Is this Accessibility cheat sheet free to use?
Where do the examples come from?
How do I go deeper than a cheat sheet?
Related cheat sheets
SEO Basics Domains & DNS Web Hosting CDNs & Caching
Last refreshed 2026-09-27.