Cheat SheetsHTML, CSS & ResponsiveAccessibility

Accessibility — Cheat Sheet

HTML, CSS & Responsive · 5 topics. Download the PDF or the Instagram carousel and share it.

Cheat Sheet · AiCanCode.org
Accessibility
HTML, CSS & Responsive5 topicsQuick revision reference
1

Accessible Structure — What a Screen Reader Hears

A screen reader builds its own picture of your page from the accessibility tree. Understanding what that tree contains explains why semantic markup is not a formality.

  • Assistive technology reads a role, name, state and value for each element — a div supplies none of them
  • The accessible name has a priority order, so aria-label silently overrides visible text
  • Users navigate by headings, landmarks, links and form fields rather than reading linearly
  • A skip link as the first focusable element lets keyboard users bypass navigation on every page
  • .sr-only hides visually but keeps content announced; aria-hidden on a focusable element creates an unusable control
Role, name, state — inspect it in DevTools
<button aria-pressed="true">Bookmark</button>
// role: button   name: "Bookmark"   state: pressed

<input type="checkbox" id="pro" checked>
<label for="pro">Pro only</label>
// role: checkbox   name: "Pro only"   state: checked

<div onclick="…">Bookmark</div>
// role: generic   name: (none)   state: (none)
// Announced as nothing. Not reachable. Not operable.

/* Inspect it: DevTools -> Elements -> Accessibility panel shows the
   computed role and name for the selected element. If the name is
   empty or the role is "generic" on something interactive, that is
   your bug, visible before any screen reader is involved. */

/* The accessible NAME is computed in a fixed priority order:
     aria-labelledby > aria-label > the native label / content >
     title attribute
   So an aria-label silently overrides visible text — which is why a
   button reading "Save" can announce something completely different. */

<button aria-label="Close">Save</button>    // announces "Close". Bug.
2

Keyboard Access and Focus Management

Everything doable with a mouse must be doable with a keyboard. Test it by unplugging the mouse — most sites fail within a minute.

  • outline: none without a replacement is the most damaging common CSS line; :focus-visible is the correct tool
  • A focus indicator needs about 2px and 3:1 contrast to satisfy WCAG 2.2
  • Only tabindex 0 and -1 are acceptable — positive values corrupt the page's tab order
  • Tab order follows the DOM, so visual reordering with CSS desynchronises it from what users see
  • When a dialog opens, content appears or a row is deleted, focus must be moved deliberately — including on SPA route changes
:focus-visible, and never a bare outline: none
/* This appears in countless codebases and breaks keyboard use */
*:focus { outline: none }

/* If you remove it, you must replace it */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 2px;
}
:focus:not(:focus-visible) { outline: none }   /* mouse clicks only */

/* :focus-visible applies for keyboard focus and not for mouse
   clicks, which is exactly the behaviour people wanted when they
   removed outlines. */

/* WCAG 2.2 requires the focus indicator to be at least 2px thick
   and have 3:1 contrast against the adjacent colour. On a dark
   background a dark outline is as good as none. */

/* Make it work on both light and dark: */
:focus-visible { outline: 2px solid; outline-color: currentColor }
/* or a two-tone ring that shows on any background: */
:focus-visible { box-shadow: 0 0 0 2px var(--bg), 0 0 0 4px var(--accent) }

/* Also ensure the focused element is not hidden under a sticky
   header when it scrolls into view: */
html { scroll-padding-block-start: 5rem }
3

ARIA — When to Use It, and When Not To

ARIA changes what assistive technology reports without changing behaviour. That asymmetry is why incorrect ARIA is worse than none — it makes a promise the code does not keep.

  • ARIA changes what is announced but never adds behaviour — a role without keyboard support is a broken promise
  • The first rule of ARIA is to use a native element instead; pages with ARIA average more errors than pages without
  • aria-expanded, aria-current, aria-pressed and aria-describedby must be kept in sync, and a stale state is worse than none
  • ARIA attributes double as CSS selectors, which keeps styling and semantics from drifting apart
  • A live region must already exist in the DOM before its text changes; role="alert" interrupts and should be reserved for errors
Native element first, always
/* 1. Use a native element if one exists.
      <button> over <div role="button"> — every time. */

/* 2. Do not change native semantics without a very good reason. */
<h2 role="button">        // now it is not a heading in the outline
<button role="heading">   // now it is not in the buttons list

/* 3. Every interactive ARIA control must be keyboard operable.
      A role is a promise; the key handler is keeping it. */

/* 4. Do not put aria-hidden on a focusable element — it creates a
      control that can be reached but not perceived. */

/* 5. Every interactive element needs an accessible name. */
<button aria-label="Close dialog"><svg aria-hidden="true"/></button>

/* The measurable consequence: surveys of the top million sites
   consistently find pages WITH ARIA average more detected errors
   than pages without, because it is applied incorrectly. Reach for
   it deliberately, not decoratively. */

/* If you are implementing a combobox, tree or complex menu, follow
   the ARIA Authoring Practices Guide exactly — or use a headless
   library that already has. These patterns have a lot of required
   keyboard behaviour that is easy to get subtly wrong. */
4

Visual Accessibility — Contrast, Zoom and Motion

Low vision, colour vision deficiency and motion sensitivity affect a large share of users. Each has a specific, testable requirement — and meeting them improves the design for everyone.

  • WCAG AA is 4.5:1 for body text and 3:1 for large text and UI components — DevTools shows the ratio directly
  • Colour must never be the only signal; pair it with an icon, text or shape
  • A page must work at 200% zoom, and text must scale — which px-based font sizes and fixed heights prevent
  • prefers-reduced-motion should reduce movement rather than remove all feedback, and long animations need a pause control
  • Line length around 65 characters, line-height 1.5 and left alignment materially help dyslexic and low-vision readers
4.5:1, and never colour alone
/* WCAG AA — the working minimum:
     body text                4.5:1
     large text (>=24px, or >=18.66px bold)   3:1
     UI components, borders, icons, focus rings   3:1
   AAA is 7:1 for body text, worth aiming at for long reading. */

/* The usual failures:
     grey placeholder text on white       (#999 on #fff = 2.8:1)
     white text on a mid-tone brand colour
     disabled state so faint it is unreadable
     text over a photograph with no scrim                          */

/* Check: DevTools colour picker shows the ratio and an AA/AAA badge
   directly in the swatch. There is no excuse for guessing. */

/* Never rely on colour alone — about 1 in 12 men has a colour
   vision deficiency, and red/green is the common one: */
<span class="error">✕ Failed — 2 tests did not pass</span>
/* icon + text + colour, so the meaning survives without the colour */

/* Charts: use pattern, label or shape in addition to hue. */

/* Test it: DevTools -> Rendering -> Emulate vision deficiencies
   (protanopia, deuteranopia, blurred vision). */
5

Testing Accessibility

Automated tools catch roughly a third of issues. The rest need a keyboard, a screen reader and about ten minutes — which is still far cheaper than the alternative.

  • Automated tools catch roughly a third of issues — a Lighthouse score of 100 does not mean an accessible page
  • axe in CI and eslint-plugin-jsx-a11y while authoring catch mechanical problems early and cheaply
  • The manual checklist is keyboard, 200% zoom, larger default font, CSS off, images off, and a few minutes with a screen reader
  • WCAG 2.2 level AA is the practical target that most legal requirements reference
  • Building with the correct native element from the start removes most of what remediation work consists of
axe in CI, jsx-a11y while writing
# In the browser
axe DevTools extension           # the most thorough, best explanations
Lighthouse -> Accessibility      # a quick score, fewer checks
WAVE                             # visual overlay of issues

# In tests
npm i -D @axe-core/playwright
const results = await new AxeBuilder({ page }).analyze()
expect(results.violations).toEqual([])

# Component tests
import { axe } from 'jest-axe'
expect(await axe(container)).toHaveNoViolations()

# Linting, at authoring time
eslint-plugin-jsx-a11y           # catches missing alt, invalid ARIA,
                                 # click handlers without key handlers

/* What they DO catch: missing alt, low contrast, missing labels,
   invalid ARIA, duplicate ids, missing lang, empty links.

   What they CANNOT catch: alt="image" on a chart, a tab order that
   makes no sense, a modal that does not trap focus, a "Submit"
   button that deletes an account, motion that causes nausea, or
   heading levels chosen for their size.                          */
Learn this free with Aria, your AI tutor → AiCanCode.org/learn/html-css