To improve website accessibility, fix the most common failures first: raise text contrast to at least 4.5:1, give every meaningful image alt text, label every form field, give every link and button a name, and declare the page language. Then make everything work from the keyboard with visible focus, and test with a screen reader.
Those priorities come from data, not taste. WebAIM's February 2026 scan of the top one million home pages found detectable WCAG failures on 95.9% of them, and 96% of the errors it detected fell into six types. This guide fixes those six first, with the HTML and CSS for each, then covers what scanners cannot judge (keyboard access, structure, captions, zoom, motion and target size), how to test your own site, and a checklist in priority order. For the design rules behind a new site, see our web design tips for beginners.
Start with the six failures found on most home pages
Accessibility work reaches a large audience: the World Health Organization estimates that 1.3 billion people, about 1 in 6, experience significant disability. The barriers they meet are remarkably repetitive. WebAIM runs its WAVE engine over the home pages of the top million sites every year; in 2026 it detected 56.1 errors per page on average, and the same six failure types have topped the list for seven years:
| Failure (WebAIM Million, February 2026) | Home pages affected | WCAG 2.2 success criterion | The fix |
|---|---|---|---|
| Low-contrast text | 83.9% | 1.4.3 Contrast (Minimum), AA | Text at 4.5:1 against its background |
| Missing alternative text for images | 53.1% | 1.1.1 Non-text Content, A | An alt attribute on every <img> |
| Missing form input labels | 51% | 1.3.1 Info and Relationships, 3.3.2 Labels or Instructions, A | A <label> tied to each field |
| Empty links | 46.3% | 2.4.4 Link Purpose (In Context), A | Text or alt text inside every link |
| Empty buttons | 30.6% | 4.1.2 Name, Role, Value, A | Text or aria-label on every button |
| Missing document language | 13.5% | 3.1.1 Language of Page, A | <html lang="en"> on every page |
These are only the failures software can detect, so they undercount: WebAIM itself notes that a page with no detected errors is not necessarily accessible. They also cover home pages only, so check your forms, checkout and account pages too.
The criterion numbers come from WCAG 2.2, the W3C's Web Content Accessibility Guidelines, which grade each success criterion level A, AA or AAA. Accessibility is also a legal matter in many places (in the EU, the European Accessibility Act covers services such as e-commerce and banking), but formal conformance, audits and the law are covered in our guide to making a website WCAG compliant; this guide is about the fixes.
Give every image a text alternative
WCAG 1.1.1 (level A) asks for a text alternative that serves the same purpose as each image. In HTML that is the alt attribute, and what goes in it depends on the image's job. The W3C's alt decision tree comes down to three cases:
- Informative images get a short description of what the image tells the reader, not an inventory of every pixel.
- Functional images, inside a link or a button, describe the action. The W3C's functional images tutorial gives the examples: "search" rather than "magnifying lens", and the site's name plus "homepage" for a linked logo.
- Decorative images get an empty
alt="", so screen readers skip them. Leaving the attribute out is not the same thing: without it, some screen readers announce the file name instead.
<img
src="office-map.png"
alt="Map: the office is on the north side of Main Street, beside the train station."
/>
<a href="/"><img src="logo.svg" alt="Example.com homepage" /></a>
<button type="submit"><img src="search.svg" alt="Search" /></button>
<img src="divider-wave.svg" alt="" />
Start with linked images. In WebAIM's 2026 data, 16.2% of home page images had no alt text, and 45% of those were linked images, which leaves the link with no name at all. Another 10.8% of the images that did have alt text used filler such as "image", "graphic" or a file name. A banner that is a picture of words fails twice: the words must be in the alt text, and WCAG 1.4.5 (AA) asks for real text wherever the design can be built with it.
Write alt text for the person listening, not for search engines. Google does use alt text, with computer vision and the page's content, to understand an image, but warns that stuffing alt attributes with keywords can make a site look like spam; our guide to improving SEO covers image search.
Fix text and interface colour contrast
Low-contrast text is the most common failure by a distance: WebAIM found it on 83.9% of home pages in 2026, 34 instances per page on average. Two AA rules apply:
- 1.4.3 Contrast (Minimum): text needs a contrast ratio of at least 4.5:1 against its background, or 3:1 for large text. Large means at least 18 point, or 14 point bold, which the W3C's Understanding 1.4.3 puts at roughly 24px and 18.5px. Logos and text in disabled controls are exempt.
- 1.4.11 Non-text Contrast: whatever you need to see to identify a control and its state (a field's border, a checkbox, a toggle's position) and the parts of a chart you need to understand it must reach 3:1 against adjacent colours.
The ratio comes from the relative luminance of the two colours and runs from 1:1 (the same colour) to 21:1 (black on white), so measure it rather than judging by eye. Common greys on white, with the ratio WCAG's formula gives:
.caption {
color: #999999;
} /* 2.85:1, fails 1.4.3 */
.caption {
color: #767676;
} /* 4.54:1, passes for body text */
input {
border: 1px solid #cccccc;
} /* 1.61:1, fails 1.4.11 */
input {
border: 1px solid #8a8a8a;
} /* 3.45:1, passes */
Chrome DevTools lists every low-contrast element in its CSS Overview panel and in a Lighthouse report, and its colour picker suggests the nearest colour that passes. For a design file, WebAIM's Contrast Checker takes two hex values and reports the text and graphics thresholds. Check text on photos against the lightest area behind it, and check hover, focus and error states too.
A sibling rule, 1.4.1 Use of Color (level A), says colour cannot be the only visual means of conveying information. A link that differs from the paragraph only by colour, or an error shown only as a red border, fails for anyone who cannot see the difference: add an underline, an icon or words.
Make everything work from the keyboard, with visible focus
Keyboard access matters to the many users with motor disabilities who rely on a keyboard, as WebAIM's keyboard guide notes, and to screen reader users, who navigate by keyboard too (web.dev). WCAG 2.1.1 (level A) requires all functionality to work from a keyboard, 2.1.2 (A) forbids keyboard traps, and 2.4.3 (A) asks for a focus order that preserves meaning.
The cheapest way to pass is to use the elements browsers already make operable. Of the two lines below, only the second works: a <div> with a click handler is not in the tab order, while a <button> is reached with Tab, pressed with Enter or Space, and announced as a button. The W3C's first rule of ARIA use generalizes it: if a native HTML element has the behaviour you need, use it rather than adding a role to something else.
<div class="btn" onclick="save()">Save</div>
<button type="button" onclick="save()">Save</button>
Avoid tabindex values above 0, which the web.dev guide warns change the tab order and confuse screen reader users, and do not make headings or other text focusable.
Keep the focus indicator visible
WCAG 2.4.7 Focus Visible (AA) requires a visible indicator on the element with keyboard focus. Browsers draw one by default, and Understanding 2.4.7 lists the common way to lose it as a named failure (F78): styling outlines and borders so the indicator disappears, typically outline: none with nothing put back. A restyled indicator is non-text content, so the 3:1 contrast rule applies to it too. The :focus-visible selector applies when the browser judges that focus should be shown, typically for keyboard use rather than mouse clicks:
:focus-visible {
outline: 3px solid #1d4ed8; /* 6.7:1 on white */
outline-offset: 2px;
}
html {
scroll-padding-top: 5rem; /* the height of the sticky header */
}
The second rule serves a criterion new in WCAG 2.2, 2.4.11 Focus Not Obscured (Minimum) (AA): a focused element must not be entirely hidden by content you added. The Understanding document names sticky headers, sticky footers and non-modal dialogs as the usual culprits (a cookie banner fails if it covers the focused element completely) and lists CSS scroll-padding as a sufficient technique. For the indicator's design, the AAA criterion 2.4.13 Focus Appearance sets a good target: at least the area of a 2 CSS pixel outline around the control, with 3:1 contrast between focused and unfocused states.
Add a skip link
WCAG 2.4.1 Bypass Blocks (A) asks for a way past blocks repeated on every page, such as the navigation, and a "Skip to main content" link as the first focusable element is the standard answer. WebAIM found one on only 17.1% of home pages, and one in ten of those was broken: hidden in a way that made it unusable, or pointing at a missing target. Move it off-screen until it receives focus, rather than using display: none, which removes it from the tab order:
.skip-link {
position: absolute;
left: 1rem;
top: -4rem;
}
.skip-link:focus {
top: 1rem;
}
Structure pages with headings, landmarks and a language
Screen reader users move around a page by its structure: WebAIM describes headings as the primary way they navigate, and landmarks give them a second map. Get three things right:
- Headings. One
<h1>for the page's topic, then<h2>and<h3>in outline order. The W3C's headings tutorial says to avoid skipping ranks, such as an<h2>followed directly by an<h4>. Choose the level by structure and set the size in CSS. WebAIM found skipped levels on 41.8% of home pages and no headings at all on 7.5%. - Landmarks. The
<header>,<nav>,<main>,<aside>and<footer>elements mark page regions for browsers and assistive technology without any ARIA. When a page has more than one<nav>, give each a label. Only 46.1% of home pages had a<main>element or main landmark. - Language.
<html lang="en">(3.1.1, level A) lets screen readers load the correct pronunciation rules and browsers display characters correctly. A passage in another language gets its ownlangattribute (3.1.2, AA).
<!doctype html>
<html lang="en">
<body>
<a class="skip-link" href="#main">Skip to main content</a>
<header>...</header>
<nav aria-label="Main">...</nav>
<main id="main">
<h1>Pricing</h1>
<h2>Plans</h2>
<h3>Starter</h3>
<p lang="fr">Tous les prix sont en dollars canadiens.</p>
</main>
<footer>...</footer>
</body>
</html>
A screen reader user can then list the headings as a table of contents, jump from landmark to landmark, or follow the skip link straight past the navigation.

Give every link and button a name that says what it does
As the page loads, the browser builds an accessibility tree: the subset of the DOM that assistive technology uses, where each link and button carries a role and an accessible name. The name comes from the element's text, the alt of an image inside it, aria-label or aria-labelledby, and a screen reader announces it with the role. An icon-only control with none of those has no name, and Deque's rule for it puts the result plainly: screen reader users cannot discern its purpose. For a linked image without alt, the W3C adds that screen readers typically fall back to the file name or a URL. These are what WebAIM counts as empty links (46.3% of home pages) and empty buttons (30.6%).

Give icon controls a name, and hide the decorative icon inside them from assistive technology with aria-hidden:
<button type="button" aria-label="Search">
<svg aria-hidden="true" viewBox="0 0 24 24">...</svg>
</button>
<a href="/cart/">
<svg aria-hidden="true" viewBox="0 0 24 24">...</svg>
<span class="visually-hidden">Cart</span>
</a>
<a href="/pricing/"
>Read more<span class="visually-hidden"> about pricing</span></a
>
.visually-hidden {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
}
Never put aria-hidden="true" on the focusable element itself: the W3C's fourth rule of ARIA use forbids it, because some users would then focus on "nothing". Chrome DevTools shows any element's computed name in its Accessibility tab.
Three rules keep names useful:
- Link text should make sense in context (2.4.4, level A). WebAIM found ambiguous link text such as "click here", "more" or "continue" on 15.2% of home pages. Descriptive visible text is best; the hidden text in the last example is a fallback for card layouts.
- The name must contain the visible label (2.5.3 Label in Name, level A). A button that shows "Send" but is named
aria-label="Submit form"breaks voice control, because people use the visible label in their spoken commands. - ARIA is not a shortcut. Home pages that used ARIA averaged 59.1 detected errors, against 42 on pages without it, although WebAIM notes that those pages were also more complex. The ARIA Authoring Practices Guide opens with a warning that no ARIA is better than bad ARIA: a role promises behaviour that you then have to build.
Label form fields and write errors people can act on
WebAIM found missing labels on 51% of home pages, and a third (33.1%) of all form inputs were not properly labelled. It counted only a <label>, aria-label, aria-labelledby or title, so placeholder text alone does not count. A label does two jobs, as the W3C's forms tutorial explains: assistive technology announces it with the field, and browsers turn it into a larger clickable area. The label's for attribute must exactly match the field's id.
Errors need the same care. WCAG 3.3.1 Error Identification (A) says a detected error must identify the field and describe the problem in text, and 3.3.3 Error Suggestion (AA) asks you to say how to fix it when you know. Attach the message to its field with aria-describedby, as the W3C's notifications tutorial shows, and mark the field with aria-invalid="true". For personal details, the autocomplete attribute meets 1.3.5 Identify Input Purpose (AA), which asks that each field's purpose be programmatically determinable.
<label for="email">Email address</label>
<input
id="email"
name="email"
type="email"
autocomplete="email"
aria-invalid="true"
aria-describedby="email-error"
/>
<p id="email-error" class="field-error">
Enter an email address in the format name@example.com.
</p>
On a long form, the W3C tutorial also recommends listing the errors at the top of the page under a clear heading, each naming the field, explaining the fix and linking to the field. Because of 1.4.1, a red border is never the only signal.
Caption video and transcribe audio
WCAG splits media into a few clear requirements:
| Media | Requirement | WCAG 2.2 criterion and level |
|---|---|---|
| Prerecorded video with sound | Captions | 1.2.2, A |
| Prerecorded video with sound | Audio description of important visual information | 1.2.5, AA |
| Live video with sound | Captions | 1.2.4, AA |
| Prerecorded audio only, such as a podcast | A transcript | 1.2.1, A |
Captions carry both speech and the non-speech sounds needed to follow the content, synchronized with the media. Automatic captions are a starting point, not the finished job: the W3C's captions guide says they do not meet user needs or accessibility requirements unless confirmed to be fully accurate, and usually need significant editing. Its own example shows an automatic caption turning a cooking time of 4 to 5 minutes into 45 minutes and dropping a "not" from the next instruction. For podcasts, the W3C's transcripts guide confirms that a prerecorded audio-only file needs a transcript at level A.
On the web, captions are usually WebVTT (.vtt) files attached with the <track> element:
<video controls src="product-tour.mp4">
<track
kind="captions"
src="product-tour.en.vtt"
srclang="en"
label="English"
default
/>
</video>
Let text zoom to 200% and content reflow at 400%
People with low vision zoom in, and three AA criteria make sure the page survives it:
- 1.4.4 Resize Text: text can be resized to 200% without loss of content or functionality.
- 1.4.10 Reflow: content works without scrolling in two directions at a width of 320 CSS pixels, which the W3C's Understanding 1.4.10 equates to a 1280-pixel-wide window at 400% zoom. Maps, diagrams, video, games and data tables are allowed to scroll on their own.
- 1.4.12 Text Spacing: nothing breaks when a user sets line height to 1.5 times the font size, paragraph spacing to 2 times, letter spacing to 0.12 times and word spacing to 0.16 times.
Most failures come from fixed dimensions: a container with a fixed pixel width, a card with a fixed height that clips enlarged text, a sticky header that fills half the screen at 400%. Let boxes grow with their content:
<meta name="viewport" content="width=device-width, initial-scale=1" />
img,
video {
max-width: 100%;
height: auto;
}
.card {
min-height: 12rem;
} /* not height: the card grows with its text */
.table-wrap {
overflow-x: auto;
} /* a data table may scroll on its own */
Never add user-scalable=no to the viewport tag. MDN warns that disabling zoom prevents people with low vision from reading the page, and iOS 10 and later ignore it by default anyway. To test reflow, make the browser window 1280 pixels wide and zoom to 400%: the page should turn into a single column with no horizontal scroll bar.
Respect motion settings
Movement on a page is covered by three criteria:
- 2.2.2 Pause, Stop, Hide (A): anything that moves, blinks or scrolls automatically for more than five seconds alongside other content needs a way to pause, stop or hide it. Carousels, background videos and news tickers are the usual cases.
- 2.3.1 Three Flashes or Below Threshold (A): nothing flashes more than three times in any one second, unless the flash is below the general and red flash thresholds.
- 2.3.3 Animation from Interactions (AAA): motion triggered by an interaction can be turned off unless it is essential. The Understanding document explains why: for people with vestibular (inner ear) disorders, motion such as parallax scrolling can trigger dizziness, nausea and headaches.
Operating systems let people ask for less motion: Settings > Accessibility > Visual effects > Animation effects on Windows 11, Reduce motion in the Accessibility settings on macOS, Settings > Accessibility > Motion on iOS and Remove animations on Android 9 and later. Browsers pass that choice to CSS as prefers-reduced-motion. The safest pattern makes motion opt-in: animations live inside a no-preference query, so anyone who asked for reduced motion gets a still page without any extra code:
@media (prefers-reduced-motion: no-preference) {
.reveal {
animation: slide-up 400ms ease-out both;
}
html {
scroll-behavior: smooth;
}
}
In JavaScript, window.matchMedia("(prefers-reduced-motion: reduce)").matches gives the same answer before you start an animation. The Rendering tab in Chrome DevTools can emulate the setting, so you can check the reduced version without changing your system settings.
Make touch and click targets at least 24 by 24 pixels
WCAG 2.2 added 2.5.8 Target Size (Minimum) at level AA: a pointer target must be at least 24 by 24 CSS pixels. A smaller target still passes if a 24-pixel circle centred on it does not overlap another target or its circle, if an equivalent control elsewhere on the page meets the size, if it is a link inside a sentence, or if the browser sets its size. The Understanding document names the people it protects: users with hand tremors, spasticity or quadriplegia, who find small targets placed close together hard to hit accurately. The AAA criterion 2.5.5 sets 44 by 44 CSS pixels.
Check close buttons, carousel dots, social icons and pagination links first, since they tend to be drawn at icon size. Enlarge the target with padding, not by scaling the icon:
.icon-button {
display: inline-grid;
place-items: center;
min-width: 44px; /* 24px meets 2.5.8 (AA); 44px meets 2.5.5 (AAA) */
min-height: 44px;
}
Test your site yourself: keyboard, screen reader and automated checks
Each kind of test finds problems the others miss, so run all three on your key templates before a release.
Do a keyboard-only pass
- Put the mouse aside, load the page and press Tab. The first stop should be the skip link, and you should see it.
- Tab through the whole page, and use Shift+Tab to go back. Every link, button and field should be reachable in an order that follows the layout, with a focus indicator you can see at every stop.
- Operate things with the standard keys from WebAIM's keyboard table: Enter follows a link, Enter or Space presses a button, Space toggles a checkbox, and arrow keys move within radio groups and menus.
- Open every menu, dialog and accordion. Esc should close dialogs and menus, and you should never get stuck inside one.
- Watch for focus disappearing behind a sticky header, footer or cookie banner.
Run a screen reader smoke test
You do not need to buy anything. NVDA is a free, open source screen reader for Windows that works with browsers such as Firefox and Chrome. VoiceOver is built into macOS and iPhone, and TalkBack is the Google screen reader included on Android devices.
| Screen reader | Platform | Turn it on | Move through a page |
|---|---|---|---|
| NVDA | Windows | Control+Alt+N once installed | H next heading, D next landmark, K next link, F next form field; NVDA+F7 lists elements |
| VoiceOver | macOS | Command-F5 | VO-U opens the rotor's lists of headings, links and more (VO is Control-Option, or Caps Lock) |
| VoiceOver | iPhone | Settings > Accessibility > VoiceOver | Swipe right for the next item, double-tap to activate |
| TalkBack | Android | Settings > Accessibility > TalkBack, or hold both volume buttons for a few seconds | Swipe right for the next item, double-tap to activate |
In NVDA, the Insert key is the NVDA key by default and Control stops speech. With the screen reader on, answer five questions for each key template:
- Does the page title, then the
<h1>, say where you are? - Does the list of headings read like a table of contents?
- Does every link and button announce a name that says what it does, followed by its role?
- Does every field announce its label, and is the error message read when you return to a field in error?
- Is informative alt text read out, and are decorative images silent?
Run automated checks, and know their limits
Automated checkers find the mechanical failures in seconds. axe-core, Deque's open source engine, powers the axe browser extension and plugs into unit, integration and browser tests; Lighthouse, as web.dev notes, uses it for its accessibility audits. WebAIM's WAVE has extensions for Chrome, Firefox and Edge and is the engine behind the WebAIM Million. Run one on every template, and put axe in your pipeline so regressions fail the build; our overview of test automation trends covers where those checks fit.
Then read the fine print the tool makers publish:
- Deque says that with axe-core you can find on average 57% of WCAG issues automatically; the rest need a person.
- Lighthouse's own report states that automatic detection can only detect a subset of issues and does not guarantee accessibility. Its accessibility score is a weighted average of pass-or-fail automated audits, and manual checks do not count toward it, so a score of 100 is not a verdict.
- When the UK Government Digital Service planted 143 barriers on a test page in 2017 and ran ten tools against it, 42 barriers, 29%, were missed by every tool. They included links identified by colour alone and tables with empty cells.

The W3C puts it plainly in its guide to selecting evaluation tools: tools save time, but some accessibility checks cannot be automated at all.
A website accessibility checklist, in priority order
Work down the list; the first five cover the six failures WebAIM finds most often, and each is often a one-line fix.
| # | Check | WCAG 2.2 | How to verify |
|---|---|---|---|
| 1 | Text contrast at 4.5:1, or 3:1 for large text | 1.4.3 (AA) | DevTools CSS Overview or a contrast checker |
| 2 | Alt text on informative images, alt="" on decorative ones | 1.1.1 (A) | Automated scan, then read the alt texts |
| 3 | A label on every field, errors described in text | 1.3.1, 3.3.1, 3.3.2 (A); 3.3.3 (AA) | Screen reader; submit the form empty |
| 4 | A name on every link and button | 2.4.4, 4.1.2, 2.5.3 (A) | Automated scan; DevTools Accessibility tab |
| 5 | lang on the <html> element | 3.1.1 (A) | View source |
| 6 | Everything works by keyboard, with no traps | 2.1.1, 2.1.2, 2.4.3 (A) | Keyboard-only pass |
| 7 | Focus always visible, never hidden by sticky content | 2.4.7, 2.4.11 (AA) | Keyboard pass with the cookie banner open |
| 8 | Headings in order, landmarks, a working skip link | 1.3.1, 2.4.1 (A); 2.4.6 (AA) | Screen reader heading and landmark lists |
| 9 | Control borders, states and chart parts at 3:1; colour never alone | 1.4.11 (AA); 1.4.1 (A) | Contrast checker; DevTools vision deficiency emulation |
| 10 | Captions, audio description and transcripts | 1.2.1, 1.2.2 (A); 1.2.4, 1.2.5 (AA) | Watch with the sound off |
| 11 | Text at 200% and reflow at 320 CSS pixels | 1.4.4, 1.4.10, 1.4.12 (AA) | A 1280-pixel window at 400% zoom |
| 12 | Moving content can be paused; reduced motion respected | 2.2.2, 2.3.1 (A); 2.3.3 (AAA) | DevTools Rendering tab emulation |
| 13 | Targets at least 24 by 24 CSS pixels, or spaced apart | 2.5.8 (AA) | Measure in DevTools |
Tip
Fix components, not pages. A contrast, name or focus fix made once in the shared button, link, field and header components repairs every page that uses them.
Fixing an existing site is one project; keeping it fixed as pages are added is another. Our web design and development team builds with semantic HTML, keyboard paths, contrast and focus checked against WCAG 2.2 AA in every pull request. For a site that already exists, the first step is a website audit, which includes accessibility findings against WCAG 2.2 AA alongside speed and search health.


