Guide · 10 min read · Updated

An HTML accessibility checklist for beginners (WCAG 2.2)

Illustration of an accessibility checklist with icons for sight, hearing, keyboard and touch

Unplug your mouse and try to send a message through your own contact form. If you get stuck on the second field, a keyboard-only visitor gets stuck there too, and so does a screen reader user, someone zoomed to 200% because of low vision, or someone squinting at a phone in bright sunlight. Accessibility means all of those people can use the page. The yardstick is WCAG 2.2 (Web Content Accessibility Guidelines) (opens in a new tab), published by the W3C, and most laws and public-sector policies point to its level AA.

Most of the fixes are ordinary HTML written correctly. Below are the checks you can make in your own markup, each tagged with its WCAG success criterion number so you can look it up, followed by a short routine for testing with a keyboard and a screen reader.

Page language and title

  • Put the page language on the <html> element (WCAG 3.1.1, Language of Page). A screen reader uses it to choose a voice; without it, an English voice will happily mangle a German page. Passages in another language get their own attribute, as in <span lang="fr">bonjour</span> (3.1.2).
  • Give each page its own descriptive title (2.4.2, Page Titled). A screen reader announces it first, and it is the text on the browser tab.
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Contact us - Bake School</title>
</head>

Leave maximum-scale=1 and user-scalable=no out of the viewport tag. They stop people from pinch-zooming, which fails WCAG 1.4.4 (Resize Text). The lesson on The Page Skeleton goes over the rest of the head.

Headings, landmarks and a skip link

  • Every visual heading is a real <h1> to <h6>, not a bold paragraph (1.3.1, Info and Relationships).
  • Use one <h1> and do not skip levels on the way down. Each heading should say what its section contains (2.4.6, Headings and Labels).
  • Wrap the page regions in landmarks: <header>, <nav>, <main>, <footer>, <aside>. Screen reader users jump between them the way sighted users glance around.
  • Add a skip link so keyboard users do not have to tab through 20 menu items on every page (2.4.1, Bypass Blocks). Landmarks and headings count towards this criterion as well.
<body>
  <a href="#main" class="skip-link">Skip to main content</a>
  <header>...</header>
  <main id="main">
    <h1>Contact us</h1>
  </main>
</body>

You can hide the skip link visually until it gets focus, but it has to appear when someone tabs to it. Practise with Heading Order and Landmarks; the semantic HTML guide covers every landmark element.

Alt text that fits the image's job

Every <img> needs an alt attribute (1.1.1, Non-text Content). The right text depends on what the image is for:

Image typealt text
Informative photoA short description of what matters: alt="Volunteer handing a loaf to a customer"
DecorativeEmpty: alt="" (the attribute must still be present)
Image inside a link or buttonWhere it goes or what it does: alt="Home", not "logo"
Image of textThe same text as in the image
Chart or diagramA brief summary in alt, with full details in nearby text or a table

Skip "image of" and "picture of". The screen reader already says "image". A missing alt is worse than an empty one: many screen readers fall back to the file name, and nobody wants to hear IMG_4037.JPG spelled out character by character. Inline <svg> icons that carry meaning need a text alternative as well, either a <title> inside the SVG or a visible label. More in Good Alt Text.

Form labels, input types and error messages

Forms are where most sites fail real users, so this list is the longest.

  • Each field has a visible <label> tied to it with for and id (1.3.1, 3.3.2 Labels or Instructions, 4.1.2 Name, Role, Value). A placeholder does not count, because it vanishes the moment someone starts typing.
  • Related controls, such as a set of radio buttons, sit in a <fieldset> with a <legend>.
  • Personal data fields use the right input type and an autocomplete value (1.3.5, Identify Input Purpose), so the browser can fill them in. A generator such as HTML Input (opens in a new tab) writes input markup with the correct type, validation and autocomplete attributes.
  • Errors are described in words next to the field. A red border alone is not enough (3.3.1, Error Identification).
  • A process does not ask for the same information twice unless it has to (3.3.7, Redundant Entry, new in WCAG 2.2).
  • Logging in does not depend on memory tests or puzzles, and password managers and pasting are allowed (3.3.8, Accessible Authentication, new in 2.2).
<label for="email">Email address</label>
<input type="email" id="email" name="email" autocomplete="email" required
  aria-describedby="email-hint">
<p id="email-hint">We only use it to reply to you.</p>

Practise in Connecting Labels and Buttons and Fieldsets.

One rule covers most cases: a link takes you somewhere, a button makes something happen.

  • Use <a href="..."> when activating it changes the URL, whether to another page, a section of this page or a file download.
  • Use <button> for actions such as submitting a form, opening a menu, playing a video or adding to a cart.
  • The text of each link and button makes sense on its own (2.4.4, Link Purpose). Six links that all say "Read more" sound identical in a screen reader's link list.
  • An icon-only button still needs an accessible name, and when a button has visible text, that text must be part of its name (2.5.3, Label in Name).
<!-- Bad: not focusable, no role, no keyboard support -->
<div class="btn" onclick="openMenu()">Menu</div>
<!-- Good -->
<button type="button" aria-expanded="false" aria-controls="menu">Menu</button>
<!-- Icon-only button with a name -->
<button type="button" aria-label="Close dialog">&times;</button>

WCAG 2.2 also added 2.5.8, Target Size (Minimum). Anything clickable should measure at least 24 by 24 CSS pixels or have enough empty space around it. Anyone with a tremor, or a large thumb on a small phone, will thank you.

Keyboard access and a visible focus ring

  • Everything works from the keyboard (2.1.1, Keyboard). Native links, buttons and form controls get this for free. Homemade widgets usually do not.
  • There are no keyboard traps (2.1.2). A user can Tab out of every component, embedded video players and dialogs included.
  • Focus moves in a logical order (2.4.3). It follows the source, so keep the HTML in reading order and do not rearrange it with CSS.
  • Focus is always visible (2.4.7, Focus Visible). If you remove the default outline, replace it with something at least as obvious.
  • Focus never disappears behind a sticky header, cookie banner or chat bubble (2.4.11, Focus Not Obscured, new in 2.2).
  • Positive tabindex values such as tabindex="3" are out. tabindex="0" makes a custom control focusable, and tabindex="-1" is for elements you move focus to with script.
<style>
:focus-visible{outline:3px solid #1a5fd0;outline-offset:2px}
</style>

The lesson on Keyboard and Focus has the details.

Contrast ratios to know by heart

Contrast is set in CSS, but you should know the numbers before anyone hands you a pale grey design:

  • Normal text needs a contrast ratio of at least 4.5:1 against its background (1.4.3, Contrast Minimum).
  • Large text, meaning at least 24 px, or about 18.7 px if bold, needs at least 3:1.
  • Icons, input borders and focus indicators need at least 3:1 against the colors next to them (1.4.11, Non-text Contrast).
  • Color is never the only signal (1.4.1, Use of Color). A required field marked only in red, or a line chart told apart only by color, also needs text, an icon or a pattern.

You do not need a separate tool to check. Inspect a piece of text in Chrome or Firefox DevTools and the color picker shows the contrast ratio.

ARIA, in small doses

ARIA (Accessible Rich Internet Applications) is a set of attributes, among them role, aria-label and aria-expanded, that change what assistive technology is told about an element. It does not change behavior. Put role="button" on a <div> and the div still cannot be focused or pressed with Enter. The W3C rules of ARIA use (opens in a new tab) boil down to five points:

  1. If a native HTML element does the job, use that element and skip the ARIA.
  2. Do not override native semantics unless you have no choice (no <h2 role="tab">).
  3. Every interactive ARIA control works with a keyboard.
  4. Never put aria-hidden="true" or role="presentation" on something focusable.
  5. Every interactive element has an accessible name.

The ARIA a beginner needs is modest: aria-label to tell two <nav> elements apart, aria-describedby to tie a hint to a field, aria-expanded on a menu button and aria-live="polite" for status messages (4.1.3). Wrong ARIA does more harm than none at all, because it makes the screen reader announce things that are not true. Start with ARIA Basics.

Test with a keyboard, then a screen reader

Automated checkers such as axe DevTools, WAVE and Lighthouse take seconds and catch missing alt, missing labels and weak contrast. They find only some of the problems, so a manual pass is still needed.

Keyboard test (5 minutes)

  1. Put the mouse away and press Tab from the top of the page.
  2. Watch that focus is always visible and moves in a sensible order.
  3. Activate links with Enter, buttons with Enter and Space, and use the arrow keys in radio groups and selects.
  4. Open and close every menu and dialog. Esc should close a dialog, and focus should land somewhere sensible afterwards.

Screen reader test

  • NVDA (opens in a new tab) is free on Windows. Press H to jump between headings, D between landmarks, and Insert+F7 for a list of links and headings.
  • VoiceOver is built into macOS and iOS. On a Mac, Cmd+F5 turns it on and Ctrl+Option+U opens the rotor.
  • Narrator on Windows and TalkBack on Android come built in too.

Listen for a page title that makes sense, headings that read like an outline, images described usefully, each field announced with its label and buttons announced as buttons. Fix what you hear, test again, then try the Accessibility Rescue project.

Frequently asked questions

What is WCAG 2.2 and what changed from 2.1?

It is the current W3C recommendation for web accessibility, published in October 2023. Nearly everything from 2.1 carries over. The W3C page on what is new in WCAG 2.2 (opens in a new tab) lists nine new success criteria, among them Focus Not Obscured, Target Size (Minimum), Dragging Movements, Consistent Help, Redundant Entry and Accessible Authentication. Criterion 4.1.1 Parsing was removed as obsolete. Most organizations aim for level AA.

Does every image need alt text?

Every <img> needs an alt attribute; not every image needs a description. Informative images get a short description of what they show and why they are on this page. Decorative ones get an empty value, alt="", and screen readers skip them. Leaving the attribute out is an error, since many screen readers will read the file name instead.

When should I use ARIA in HTML?

When native HTML cannot express what you need, and not before. Try <button>, <nav> and <label> before you reach for role or aria-label. ARIA changes what assistive technology announces and never adds keyboard behavior. Sensible uses include naming two navigation regions, linking a hint with aria-describedby and showing open or closed state with aria-expanded.

What is the minimum color contrast for accessibility?

4.5:1 for normal text and 3:1 for large text at WCAG 2.2 level AA. Large means about 24 px regular or 18.7 px bold. Interface components and meaningful graphics, such as input borders, icons and focus indicators, need at least 3:1 against the colors around them. Level AAA raises the text requirement to 7:1.

How do I test my website with a screen reader?

Install the free NVDA screen reader on Windows, or turn on VoiceOver on a Mac with Cmd+F5. Open your page, move by headings and landmarks, tab through the links and form fields, and listen. The title, headings, image descriptions, labels and button names should all make sense with your eyes closed.

Practise it in the course

Try it in the playground

More guides