Tools & Workflow

Web accessibility: a practical checklist you can apply to any page today

The accessibility fixes with the widest effect, each with the code: keyboard access and focus, colour contrast, labels and names, images, headings and landmarks, motion, and how to test with a keyboard and a screen reader.

By the TechTutorZone editorial team · Published · 6 min read (1,222 words)

Accessibility is the quality of a page working for people who use it differently from you: with a keyboard instead of a mouse, with a screen reader, zoomed to 300%, with low vision, with a tremor, with a cognitive disability, or just with a cracked phone in sunlight. The relevant standard is WCAG 2.2, and its level AA is what most laws and contracts require. This tutorial is not the whole standard; it is the subset of fixes that catch most real failures, in the order worth doing them, with the code for each.

1. Everything works with a keyboard

Press Tab from the top of the page. Every link, button and form control should receive focus in a sensible order, you should be able to see which one has it, and Enter or Space should activate it. Things that break this:

  • Clickable <div>s. A <div onclick> cannot be tabbed to and has no key handling. Use <button>. If you truly cannot, you need tabindex="0", role="button" and a keydown handler for Enter and Space, which is more code than the button.
  • outline: none on focus. Removing the focus ring blinds keyboard users. Style it instead, and use :focus-visible so mouse clicks do not show it:
:focus-visible { outline: 3px solid #e36209; outline-offset: 2px; }
  • Keyboard traps: a modal that you cannot Esc out of, or a widget that swallows Tab. The native <dialog> element handles focus and Escape for you.
  • Hover-only menus. If it opens on hover, it must also open on focus or click.

Add a skip link as the first focusable element so keyboard users can jump past the header:

<a class="skip" href="#main">Skip to content</a>
<!-- ... -->
<main id="main">
.skip { position: absolute; left: -999px; }
.skip:focus { left: 8px; top: 8px; }

2. Enough contrast

Text needs a contrast ratio of at least 4.5:1 against its background (3:1 for large text, 18pt or 14pt bold and above). UI components such as input borders and icons need 3:1. Light grey placeholder text on white almost never passes; neither does white text on a mid-tone brand colour. Check pairs with the contrast checker. Do not convey information by colour alone: a red border on an invalid field needs an icon or message too, because about 8% of men cannot tell red from green.

3. Every control has a name

A screen reader announces a control by its accessible name. Inputs get it from a <label>; buttons from their text; icon buttons from nothing unless you supply it.

<!-- label by association: clicking the label focuses the input, too -->
<label for="email">Email address</label>
<input id="email" type="email" autocomplete="email">

<!-- icon-only button: give it a name -->
<button type="button" aria-label="Close dialog">
  <svg aria-hidden="true" ...></svg>
</button>

<!-- a search box with a visually hidden label -->
<label for="q" class="visually-hidden">Search</label>
<input id="q" type="search">
.visually-hidden {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap;
}

Placeholder text is not a label: it disappears when you type and is usually too faint. Error messages should be linked to the field with aria-describedby so they are read when the field is focused.

4. Images have alt text

Every <img> needs an alt attribute. If the image carries information, describe it in context ("Bar chart: sales doubled from 2024 to 2025", not "chart"). If it is decorative, alt="" tells the screen reader to skip it. Omitting the attribute altogether makes readers announce the file name. For SVG icons next to text, aria-hidden="true". For a linked image, the alt text is the link text: "Company logo" is wrong, "Home" is right.

5. Structure: headings and landmarks

Screen-reader users navigate by heading and by landmark more than by tabbing. One <h1>, levels in order without skipping, and the landmark elements (<header>, <nav>, <main>, <footer>) in place. This is covered in detail in the semantic HTML tutorial. Also set <html lang="en"> and a descriptive <title> on every page; the title is the first thing announced.

6. Text can be resized

Users zoom to 200% and more. Use rem for font sizes (never px for body text), let containers grow with content, and avoid fixed heights on anything holding text. Test at 200% browser zoom and at a 320px-wide viewport (the WCAG "reflow" criterion): nothing should need horizontal scrolling except tables and code. If you use fluid type, read the warning about vw units in the clamp() tutorial.

7. Motion and flashing

Animations can cause nausea for people with vestibular disorders, and anything flashing more than three times a second can trigger seizures. Respect the system setting:

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; }
  html { scroll-behavior: auto; }
}

Auto-playing carousels and videos need a visible pause control. Nothing should move on its own for more than five seconds without one.

8. Use ARIA sparingly and correctly

ARIA attributes (role, aria-*) add semantics that HTML lacks. The first rule of ARIA is to prefer a native element: a <button> over role="button", a <nav> over role="navigation". Where you do need it, the common correct uses are:

  • aria-label / aria-labelledby to name something that has no visible text.
  • aria-expanded="true|false" on a button that opens a menu or accordion, updated by script.
  • aria-current="page" on the navigation link for the current page.
  • aria-live="polite" on a region whose text changes by script (a status message, a search result count), so the change is announced.
  • aria-hidden="true" on decorative icons.

Wrong ARIA is worse than none. role="button" on a link that navigates, aria-label that contradicts the visible text, or aria-hidden on something focusable all actively confuse assistive technology.

Testing in ten minutes

  1. Keyboard only: unplug the mouse, Tab through the page, operate every control.
  2. Automated check: run Lighthouse's Accessibility audit, or the axe DevTools extension. They catch roughly a third of issues, reliably: missing alt, contrast, missing labels, invalid ARIA.
  3. Screen reader: VoiceOver is built into macOS (Cmd+F5) and iOS; NVDA is free on Windows. Navigate by headings (H in NVDA, the rotor in VoiceOver) and by form controls. Listen to whether each button says what it does.
  4. Zoom: 200% in the browser, then a 320px viewport in the device toolbar.
  5. Grayscale: Chrome's Rendering panel can emulate achromatopsia; is anything communicated only by colour?

Pitfalls

  • Accessibility overlays. Third-party widgets that promise to fix a site with one script do not; several have been the subject of lawsuits and complaints from the users they claim to help. Fix the HTML.
  • tabindex greater than 0. It hijacks the focus order. Use 0 or -1 only.
  • Title attributes as labels. title is not reliably announced and is invisible on touch screens.
  • Testing only with automation. A page with a perfect Lighthouse score can still be unusable by keyboard.

Exercise

Take any form you have built. Unplug the mouse and fill it in. Then turn on VoiceOver or NVDA and do it again with the screen off (or closed eyes). Make a list of every point where you were unsure what a field was for or whether a button had worked, and fix them using the sections above. Run the Lighthouse audit before and after.

Summary

  • Keyboard first: real buttons and links, visible focus, a skip link, no traps.
  • 4.5:1 contrast, and never colour alone.
  • Every control named, every image described, headings and landmarks in order.
  • Resizable text, reduced motion respected, ARIA only where HTML has no answer.
  • Test with Tab, with a screen reader, and with an automated audit; each finds different things.