WordPress Accessibility Issues and Fixes for Agencies
The WCAG failures a WordPress handover almost always contains, from the form plugin and the theme CSS to the alt text a plugin wrote, with the fix for each.
Taking over a WordPress site you did not build is mostly archaeology: a theme, a child theme somebody abandoned halfway, thirty plugins and ten years of content. The accessibility failures in that pile are not random. WordPress sites fail in the same places, because the same small set of tools writes the markup.
That is good news for an agency: if you know where the failures hide, a handover audit is a checklist instead of a hunt. This guide walks them in the order you meet them, with the markup behind each one, the WCAG criterion it breaks and the fix.
Why WordPress sites fail in predictable places
WordPress is not unusually bad. The WebAIM Million ran automated checks across one million home pages in February 2026. It detected WordPress on 252,302 of them, averaging 52.8 errors per page, which is 5.8% below the 56.1 average for the whole sample. Most pages built on a common CMS scored better than pages without one.
Better than average is still 52.8 detected errors on a home page, and the same report found detectable WCAG 2 A and AA failures on 95.9% of all pages tested. The failure types barely move year to year: low contrast text on 83.9% of pages, missing alternative text on 53.1%, missing form input labels on 51%, empty links on 46.3%, empty buttons on 30.6% and a missing document language on 13.5%.
Now map those onto an install and you can name the culprit for each. Contrast comes from the theme's color settings. Missing labels come from the form plugin. Missing alt text comes from the media library and from whatever bulk imported the images. Empty links and buttons come from icon-only controls in the theme, the slider and the social menu. That is most of the archaeology done before you open the site.
Forms: labels, errors and required fields
Contact and booking forms are the most productive place to look: almost every WordPress site has one, and almost none were written by hand. The form plugin gives you a drag and drop builder, and the builder's default field carries a placeholder rather than a label.
The placeholder that pretends to be a label
A placeholder disappears the moment somebody types. Screen reader support for it is inconsistent, and it fails 3.3.2 Labels or Instructions and 4.1.2 Name, Role, Value when it is the only thing naming the field.
<!-- Broken: the placeholder is the only thing naming this field -->
<p><input type="email" name="your-email" placeholder="Email address"></p>
<!-- Fixed: a real label, plus autocomplete so browsers can fill it -->
<p>
<label for="your-email">Email address</label>
<input type="email" id="your-email" name="your-email" autocomplete="email">
</p>
Most form plugins have a label field in the builder that is simply left empty, so the fix is usually editing the form rather than the template. Check every form on the site, not the one on the contact page: newsletter signups in the footer and search fields in the header fail the same way. Our form labels checker lists what a label has to do to count.
Errors nobody hears
The second form failure is validation. The plugin prints a message next to the field, in red, and a screen reader user gets no announcement at all because nothing told the browser the page changed. That breaks 3.3.1 Error Identification.
<!-- Broken: the message is only visible, and only red -->
<span class="wpcf7-not-valid-tip">Please enter a valid email address.</span>
<!-- Fixed: announced, tied to the field, and the field is marked invalid -->
<label for="your-email">Email address</label>
<input type="email" id="your-email" name="your-email"
aria-invalid="true" aria-describedby="your-email-error">
<span id="your-email-error" role="alert">Please enter a valid email address.</span>
Do the same for the success message. A form that confirms only in a colored box is a silent submit for anyone not watching that part of the screen.
Required fields marked only in color
A red asterisk with no text alternative tells nobody anything. Put required on the input, keep the asterisk if the client wants it, and say in words at the top of the form what the asterisk means. Our guide to accessible form design covers the rest of the pattern, including grouping and error summaries.
Themes: focus outlines, skip links and contrast
The theme is the second dig site. Three failures account for most of what you find, and all three live in CSS rather than PHP, which makes them cheap to fix in a child theme.
The focus outline somebody removed
Designers remove the focus ring because it looks wrong on a rounded button, and the line survives every theme update after that. It breaks 2.4.7 Focus Visible and makes keyboard use guesswork.
/* Broken: nothing shows where the keyboard is */
a:focus,
button:focus { outline: none; }
/* Fixed: visible on keyboard focus, invisible on mouse click */
a:focus-visible,
button:focus-visible {
outline: 2px solid currentColor;
outline-offset: 2px;
}
WordPress sets a floor here. The accessibility-ready theme guidelines require a visible focus state on all controls, "a minimum of a 2px outline surrounding the element with a contrast that meets color contrast standards against the background color". Block themes are not exempt: theme.json has no focus control, so the focus style still lives in a stylesheet somebody can delete.
Skip links that skip nothing
Most themes ship a skip link. Plenty of them hide it with display: none, which removes it from the tab order entirely, or leave it pointing at an ID the template no longer has. WordPress requires the link to be the first focusable element, be visible when keyboard focus moves to it, and move focus to the main content area when activated, which is 2.4.1 Bypass Blocks.
/* Broken: not focusable, so it does nothing for anyone */
.skip-link { display: none; }
/* Fixed: off screen until focused, then visible */
.skip-link {
position: absolute;
left: -9999px;
}
.skip-link:focus {
left: 1rem;
top: 1rem;
z-index: 100000;
}
Test it by loading the home page and pressing Tab once. If nothing appears, it is broken. Our keyboard and focus checker and the guide to keyboard navigation go through the rest of the tab order.
Contrast chosen in the customizer
Low contrast text is the most common failure on the web and on WordPress it usually has one cause: a brand color picked in the customizer or in a theme.json palette and then used for body text, placeholder text or a button label. 1.4.3 Contrast (Minimum) asks for 4.5:1 for normal text and 3:1 for large text.
Fix it in the palette rather than page by page. One darker shade added to the theme's color settings fixes every instance at once, and it survives the next content edit.
Plugins that overwrite your alt text
This one surprises people. WordPress core does not guess alt text. wp_get_attachment_image() reads the _wp_attachment_image_alt meta field and nothing else, with no fallback to the caption or the title:
'alt' => trim( strip_tags( get_post_meta( $attachment_id, '_wp_attachment_image_alt', true ) ) ),
So an image whose alt field was never filled in comes out as alt="". That markup means "this image is decorative, skip it", which is a correct and useful thing to say about a spacer and a lie about a product photo. An automated scan will not flag it, because an empty alt is valid. You have to look.
The second half of the problem is plugins that write into that field for you. Gallery, SEO and media library plugins offer to fill alt text in bulk from the file name or the post title, so every image announces itself as "DSC-4831" or repeats the post title nine times. That fails 1.1.1 Non-text Content just as surely as no alt text, and it is harder to spot because the attribute is present.
On a handover, sort the media library by date, sample twenty images across the range, and read the alt text out loud. If it sounds like a file name or a slogan, a plugin wrote it. Our alt text checker finds the empty and duplicated cases, and how to write alt text covers what to put there instead.
Cookie banners and popups that trap focus
Consent banners, newsletter popups and age gates are almost always third party, injected late, and never tested with a keyboard. Two failures repeat.
The first is a real trap: focus enters the banner and Tab cycles inside it forever with no way to reach the page and no way to dismiss it from the keyboard. That is 2.1.2 No Keyboard Trap, and it makes the whole site unusable rather than merely awkward.
The second is subtler and newer. A sticky bar pinned to the bottom of the viewport covers the element that has just received focus, so a keyboard user tabs into a link they cannot see. WCAG 2.2 added 2.4.11 Focus Not Obscured (Minimum) for exactly this. Tab down the page with the banner showing and watch whether the focused element ever disappears behind it.
If the plugin cannot be fixed it can usually be replaced. Raise that with the client early: a consent banner is a legal decision on their side, not only a technical one.
Headings picked for size
In the block editor the heading level is a dropdown next to the font size, so authors pick the level that looks right. Years of that produces pages that open with an H3, jump from H2 to H4, and use a heading to make one line bold.
The structure is what a screen reader user navigates by, which is why it falls under 1.3.1 Info and Relationships. Fix the templates first, since one bad H1 in header.php or a template part affects every page, then work through the top landing pages by traffic. Do not try to fix a thousand posts.
A 30 minute handover check
Before you quote for remediation, run this on the home page, one landing page, one blog post and the contact page. It is enough to size the job.
- Press Tab from the top. Does a skip link appear? Can you see where focus is at every stop? Does focus ever get stuck or disappear behind a sticky bar?
- Open the contact form. Click each label and check the field focuses. Submit it empty and see whether the errors are announced, not just colored.
- View source on one content page and read the heading levels in order.
- Sample twenty media library images and read their alt text.
- Check the theme's color palette against 4.5:1 for body text, including link text on colored backgrounds and button labels.
- Dismiss the cookie banner by keyboard alone.
- Confirm
<html lang>is set, and set correctly for the site's language. - List the plugins that output front end markup: forms, sliders, galleries, consent, search. Those are your remediation dependencies, and some of them will need replacing rather than patching.
Where a scan fits in
Steps 3, 4 and 5 are the ones worth automating, because they regress every time somebody publishes a post. AccessGuard runs automated WCAG 2.2 AA checks in a real browser, keeps every client site in one account with the new and fixed failures since the last scan, and produces white-label PDF reports under your agency's name. On paid plans it writes the fixes and rescans the pages you add on a schedule.
Automated checks find many real problems and cannot confirm conformance. Whether alt text is accurate, whether focus order makes sense in a real flow and whether an error message actually helps still need a person, which is why the tab-through in step 1 is first on the list rather than last.
To see where a site stands before the handover call, run a free accessibility scan on its busiest page. If the client is on Shopify rather than WordPress, the same exercise for that platform is in Shopify accessibility: 9 WCAG failures themes repeat.