---
title: "WordPress accessibility remediation: Claude and Checker Pro"
date: 2026-10-20
author: "Courtney Robertson"
featured_image: "https://courtneyr.dev/wp-content/uploads/2026/10/WordPress-Accessibility-Remediation-Team-Poster.png"
categories:
  - name: "WordPress"
    url: "/category/wptips.md"
---

# WordPress accessibility remediation: Claude and Checker Pro

WordPress accessibility remediation went faster on my 2008 site once Claude grouped 557 open findings into 16 root causes and fixed each one at its source. It went wrong whenever Claude assumed instead of measuring. On the morning of my talk, the live site showed 779 open findings again. Here’s the method, the mistakes, and what I’d build first.

This post accompanies my talk on WordPress accessibility remediation for the [WordPress Accessibility Meetup](https://www.meetup.com/wordpress-accessibility-meetup-group/events/316255843/), hosted by Amber Hinds. Every claim links to the standard, spec, or source behind it, so you can check my reasoning instead of taking my word for it.

![Using Accessibility Checker Pro + Claude AI to Remediate a Large WordPress Site, Tue, Oct 20, 2026, 8:00 AM | Meetup](https://secure.meetupstatic.com/photos/event/5/b/8/f/600_535823439.jpeg)

Meetup

 [Using Accessibility Checker Pro + Claude AI to Remediate a Large WordPress Site, Tue, Oct 20, 2026, 8:00 AM | Meetup](https://www.meetup.com/wordpress-accessibility-meetup-group/events/316255843/)

Courtney Robertson will share a practical case study of running Equalize Digital’s Accessibility Checker Pro across her long-running WordPress site (including a large archi

 

 

 

## 🗂️ The site and the starting point

[courtneyr.dev](https://courtneyr.dev/) has run on WordPress since 2008. It has 317 published posts, a custom child theme, and a large archive of 2008 to 2013 posts. I recently moved hosts, and the September remediation happened alongside that move. My own [accessibility statement](https://courtneyr.dev/accessibility-statement/) says what I commit to, so the site needed to match it.

I ran [Accessibility Checker](https://wordpress.org/plugins/accessibility-checker/) with the Pro plugin from [Equalize Digital](https://equalizedigital.com/accessibility-checker/), and gave [Claude Code](https://claude.com/claude-code) shell access to the site through WP-CLI. I’d already written about [how I choose AI tools for editing this site](https://courtneyr.dev/2026/06/22/ai-editing-in-wordpress/). The baseline, taken on September 24, 2026:

- 5,095 rows in the Checker’s table, 557 of them open
- 11 distinct rules across 324 posts and pages
- 546 “Needs Review” warnings and 11 “Problems”

## 🛠️ How I approached WordPress accessibility remediation

1. **Read the table, not the dashboard.** I exported every open row as JSON through WP-CLI instead of working from the Issues Explorer screen.
2. **Cluster by rule plus element.** 557 rows became 16 root causes. A cluster is a better unit of work than a row, because one template usually emits a hundred of them.
3. **Fix at the emitter.** Claude changed the theme filter, pattern, or plugin that produced the markup before touching content. Content edits came second. An automatic ignore rule came last, and only for review prompts with nothing to fix.
4. **Rescan, then reconcile per row.** Claude matched baseline rows to new rows by post, rule, and element signature, never by row id, because a Pro scan deletes and reinserts rows.
5. **Keep my own rules.** No bulk dismissals to reach zero. Every dismissal carries evidence or my decision, word for word. Nothing writes to the live site unless I run it.

By the September 25 scan, the 557 baseline rows resolved to 397 fixed and 160 dismissed with evidence. Zero were open. I took a victory lap. It was premature.

## 🎬 Video: from YouTube embeds to Able Player with transcripts

**Why it matters.** Captions let people who are deaf or hard of hearing follow a video. A transcript also serves people who read with a braille display and anyone who can’t play audio. WCAG covers this across several criteria: [1.2.1 Audio-only and video-only](https://www.w3.org/WAI/WCAG22/Understanding/audio-only-and-video-only-prerecorded.html), [1.2.2 Captions](https://www.w3.org/WAI/WCAG22/Understanding/captions-prerecorded.html), [1.2.3 Audio description or media alternative](https://www.w3.org/WAI/WCAG22/Understanding/audio-description-or-media-alternative-prerecorded.html), and [1.2.5 Audio description](https://www.w3.org/WAI/WCAG22/Understanding/audio-description-prerecorded.html).

### 🔎 The first problem

In June the Checker flagged 45 “Missing Transcript” findings, and 41 were [Able Player](https://ableplayer.github.io/ableplayer/) embeds that already had captions. Able Player builds its interactive transcript in JavaScript from a [WebVTT](https://www.w3.org/TR/webvtt1/) file. The Checker’s [Missing Transcript rule](https://equalizedigital.com/accessibility-checker/missing-transcript/) looks for the word “transcript” within 25 characters of the media. It can’t see a transcript that a script builds later.

### 💡 A shortcode tip that cost me a morning

In the [Able Player WordPress plugin](https://wordpress.org/plugins/ableplayer/), `captions="ID"` puts an uploaded track under the CC button. `subtitles="ID"` doesn’t. We chased the wrong cause first, and found the right one by testing it. Joe Dolson, who maintains the plugin, also added an option in [version 2.4.0](https://github.com/ableplayer/ableplayer-wordpress/releases/tag/v2.4.0) to replace YouTube embeds on the front end. Read the [commit](https://github.com/ableplayer/ableplayer-wordpress/commit/c6fb7d6157a2fdd2be1e18244636b34756f1a0e4) and the changelog to see what it does and doesn’t cover.

### ⚠️ The mistake in my own workaround

My first fix automatically ignored a video whenever YouTube had a caption track. When we audited my dismissals again in September, we found 21 videos with only YouTube’s automatic captions. Those don’t satisfy 1.2.2, which requires captions that are accurate. Twenty three rows reopened.

### 🎙️ The caption pipeline

For the 21 videos, [yt-dlp](https://github.com/yt-dlp/yt-dlp) fetched the audio and [OpenAI Whisper](https://github.com/openai/whisper) transcribed it locally. Each WebVTT file carries a NOTE saying it’s an unreviewed machine transcript. The theme attaches the file to every player for that video, so no post content changed. That’s about 18 hours of audio, transcribed in 9 hours and 15 minutes. The site now has 80 videos with synchronized tracks.

### 🚧 What’s still open

The Whisper transcripts are unproofread. I haven’t reviewed audio description per video. One post, a song, is transcript only by my decision.

### 🔁 The vendor loop, and the test I ran

Amber opened [issue 1740](https://github.com/equalizedigital/accessibility-checker/issues/1740) in June asking Accessibility Checker to recognize Able Player transcripts. Equalize merged [PR 1741](https://github.com/equalizedigital/accessibility-checker/pull/1741) on October 1 and shipped it in [version 1.51.0](https://github.com/equalizedigital/accessibility-checker/releases/tag/v1.51.0) on October 9. That’s four months from report to release.

I tested it before retiring my workaround. Claude ran the 1.51.0 scanner and my current 1.49.0 scanner on four pages, with and without my figcaption. With the figcaption removed, both versions still flagged Missing Transcript on two pages. That was a headless run of the free scanner, with a 2.5 second wait, on four pages. I haven’t run a full site scan with 1.51.0, so I can’t tell you why. If your results differ, [tell me what you see](https://courtneyr.dev/contact/).

## 🧩 `aria-hidden`: 492 findings from a few patterns

**Why it matters.** [`aria-hidden`](https://www.w3.org/TR/wai-aria-1.2/#aria-hidden) removes an element from the accessibility tree. That’s correct for decoration and harmful for content, so the Checker asks for a review each time. See also [MDN’s reference](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Attributes/aria-hidden). The related WCAG criteria are [1.1.1 Non-text content](https://www.w3.org/WAI/WCAG22/Understanding/non-text-content.html), [1.3.1 Info and relationships](https://www.w3.org/WAI/WCAG22/Understanding/info-and-relationships.html), and [4.1.2 Name, role, value](https://www.w3.org/WAI/WCAG22/Understanding/name-role-value.html).

**One pattern, 323 rows.** The footer [h-card](https://microformats.org/wiki/h-card) photo sat beside the author link with `alt=""` and `aria-hidden`. Moving the photo inside the named link, with `alt=""`, removed the need for `aria-hidden`. One template change cleared 323 rows.

**A conflict between two rules.** The Checker’s empty alt rule passes `alt=""` only with `aria-hidden`. Its `aria-hidden` rule then flags that same image. The [W3C Nu validator](https://validator.w3.org/nu/) rejects the `role="presentation"` workaround on an image with empty alt. No valid markup satisfied both, so 104 rows were dismissed with evidence for each instance, such as the commenter’s visible name beside each avatar. For deciding what counts as decorative, the [W3C alt text decision tree](https://www.w3.org/WAI/tutorials/images/decision-tree/) and [WebAIM’s alt text guide](https://webaim.org/techniques/alttext/) helped.

**A scanner artifact.** Six rows pointed at `<html>`. Able Player creates SVG icons that it never attaches to the page, and the scanner reported them as the document element. A node that isn’t in the page isn’t in the accessibility tree, so those are false positives.

**Emoji in avatar alt text.** Fediverse display names arrive with custom emoji shortcodes like `:prami_pride:`. Those became avatar alt text, and the Checker read the underscore as a filename. The theme now strips shortcodes when a comment arrives.

## 🎨 Color contrast, in both color modes

**Why it matters.** Low contrast makes text hard to read for people with low vision, and for everyone in bright light. [1.4.3 Contrast (Minimum)](https://www.w3.org/WAI/WCAG22/Understanding/contrast-minimum.html) sets 4.5:1 for normal text. The [WebAIM contrast checker](https://webaim.org/resources/contrastchecker/) tests a pair in seconds.

- **Real failures:** table header cells measured 2.45:1 because a band color rule outranked `th h3 { color: inherit }`. Setting the color tokens to `currentcolor` fixed every path at once.
- **Dark mode needs its own scan.** The Checker has no handling for [`prefers-color-scheme`](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme), so one scan covers one mode. The dark scan found 4 more elements, including two old inline highlight spans at 1.02:1. Claude replaced them with [`<mark>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/mark), which the theme styles for both modes.
- **A false positive.** The scanner measured hero text against the cover image behind it, though the text sits 19 pixels below the image, on white. The text measured 15.67:1. We dismissed the row with the number.

## 🕳️ A scan is only as good as its coverage

597 of 927 pages had never been scanned. [Perfmatters’ Delay JavaScript](https://perfmatters.io/docs/delay-javascript/) blocked the scripts the Checker injects, so the Checker failed to scan every archive page. The totals looked clean. A clean report is easy to produce when 597 pages aren’t in it. If you run a full site scan, read its failure list before you read its totals.

## ⏪ The regression: 779 findings on the morning of the talk

In September I had zero open. On the morning of this talk, my live site’s Checker table showed 779 open rows. The site had been busy. 711 of them were one element on 711 posts: the visual copy of the cut paper archive title.

The title already printed its text in a sibling that only screen readers get. The Checker passes an `aria-hidden` element when a sibling’s class names screen reader text, and my theme used a different class name. Adding the standard class cleared it. I committed the fix, and it’s running on my dev site, with a Playwright test covering it. I haven’t deployed it yet.

That’s the h-card case again. The cause, though, is new: design work shipped after the WordPress accessibility remediation, with no check to catch it.

## 🤦 Where Claude got it wrong

Here are the ways the project embarrassed both of us:

- **It assumed YouTube’s automatic captions satisfied 1.2.2.** My second audit read each video’s caption track type and caught it.
- **It emptied the alt on linked images** to clear a “redundant alt” warning, which created a Critical error. Removing the duplicate image fixed it.
- **110 of 8,411 older dismissals concealed real defects**, such as alt text that said “IMG\_1159” and empty paragraphs. Older Bulk Ignore and Global Ignore actions, some from my own earlier passes, had covered them up. The second audit found them.
- **A clean report with 597 pages unscanned.** See above.
- **GIF alt text described the wrong clips.** Looking at the actual frames caught it.
- **Predictions reported as results.** We’d listed 141 rows as dismissed before the scan had written them.
- **It expected the 1.51.0 fix to clear my pages.** Testing showed otherwise.

Delegation needs the same discipline. This morning I had Claude hand two side tasks to helper agents. I reran their lint and tests before relying on their reports.

## 🏗️ Building it accessible from the start

WordPress accessibility remediation is a cleanup, and cleanups go stale. Here’s the order I’d build in next time.

1. **Defaults.** The first thing a developer types should already pass. [The first rule of ARIA](https://www.w3.org/TR/using-aria/#rule1) is to use a native element when one exists, and the [ARIA Authoring Practices guide](https://www.w3.org/WAI/ARIA/apg/practices/read-me-first/) explains why. A shared component library makes the accessible choice the default. The [giffgaff talk at LeadDev](https://www.youtube.com/watch?v=2t4JqgbbU9Q) is a good case study, though the team reported its own figures.
2. **Gates.** A gate refuses inaccessible output. Block validations, checks that run before a commit, and the `required` items in [The Website Specification](https://specification.website/) all work this way. I had Claude build a [Playwright](https://playwright.dev/) test that runs the Checker’s own scanner over 11 templates and fails on any element I haven’t reviewed. Its first run on my dev site failed with 10 findings nobody had looked at. That’s the gate working.
3. **Human review.** A passing score is a floor. In Helen Hou-Sandí’s WordPress Accessibility Day talk, [axe-core](https://github.com/dequelabs/axe-core) finds about 25% of the issues GitHub’s audits flag. [The talk](https://www.youtube.com/watch?v=W0Q2uA0Lxe8) also covers GitHub’s open source [accessibility scanner](https://github.com/github/accessibility-scanner).
4. **AI-generated code.** In the WordPress Accessibility Day session, I reviewed this and prompted fixes for attributes such as contrast and labels. It didn’t fix keyboard and focus behavior. Point your manual testing at menus, dialogs, tabs, and carousels.

## 🎞️ A known gap: looping GIFs

I kept eight looping GIFs and added visible descriptions. That satisfies alt text, but not [2.2.2 Pause, Stop, Hide](https://www.w3.org/WAI/WCAG22/Understanding/pause-stop-hide.html), a Level A criterion for motion that starts automatically and lasts over five seconds. I documented it as an exception. I’ve since had Claude build a click to play control that respects [`prefers-reduced-motion`](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion), has a [24 pixel minimum target](https://www.w3.org/WAI/WCAG22/Understanding/target-size-minimum.html), and shows a [visible focus ring](https://www.w3.org/WAI/WCAG22/Understanding/focus-visible.html). It passes tests on a fixture page. It hasn’t been tested on the real posts or with a screen reader, so it isn’t live.

## ✅ Takeaways from WordPress accessibility remediation

1. Fix at the emitter. One pattern cleared 323 rows, and one class name cleared 711.
2. Measure the claim: ratios, cue counts, focusable descendants.
3. Every dismissal carries evidence, and old dismissals get audited again.
4. A clean scan is only as good as its coverage and the color mode it ran in.
5. Remediation regresses. A gate keeps it fixed.
6. Test a vendor’s fix on your own pages before you retire your workaround.

If you try the same four page comparison during your own WordPress accessibility remediation, [send me what you find](https://courtneyr.dev/contact/).

## 📚 Tools, standards, and further reading

### Standards and definitions

- [WCAG overview](https://www.w3.org/WAI/standards-guidelines/wcag/) and the [WCAG 2.2 quick reference](https://www.w3.org/WAI/WCAG22/quickref/)
- [How conformance works](https://www.w3.org/WAI/WCAG22/Understanding/conformance)
- [WAI-ARIA 1.2](https://www.w3.org/TR/wai-aria-1.2/) and [Using ARIA](https://www.w3.org/TR/using-aria/)
- [WebVTT](https://www.w3.org/TR/webvtt1/)
- [G200: opening new windows and tabs only when necessary](https://www.w3.org/WAI/WCAG22/Techniques/general/G200)
- [W3C: how to evaluate accessibility](https://www.w3.org/WAI/test-evaluate/)
- [The Website Specification](https://specification.website/)

### WordPress

- [Make WordPress Accessibility team](https://make.wordpress.org/accessibility/) and its [handbook](https://make.wordpress.org/accessibility/handbook/)
- [WordPress Accessibility testing guide](https://wpaccessibility.org/docs/testing/)
- [WordPress Accessibility Day](https://wpaccessibility.day/)

### Tools I used

- [Accessibility Checker](https://wordpress.org/plugins/accessibility-checker/) and [Equalize Digital’s documentation](https://equalizedigital.com/accessibility-checker/)
- [Able Player](https://ableplayer.github.io/ableplayer/) and the [WordPress plugin](https://wordpress.org/plugins/ableplayer/)
- [yt-dlp](https://github.com/yt-dlp/yt-dlp) and [OpenAI Whisper](https://github.com/openai/whisper)
- [axe-core](https://github.com/dequelabs/axe-core), [Playwright](https://playwright.dev/), and the [W3C Nu validator](https://validator.w3.org/nu/)
- [WebAIM contrast checker](https://webaim.org/resources/contrastchecker/)

### My public repositories

- [courtneyr-child](https://github.com/courtneyr-dev/courtneyr-child): the theme, including the [`aria-hidden` test](https://github.com/courtneyr-dev/courtneyr-child/blob/main/tests/playwright/aria-hidden-sr.spec.mjs)
- [wp-release-audit-method](https://github.com/courtneyr-dev/wp-release-audit-method): includes an [accessibility guide](https://github.com/courtneyr-dev/wp-release-audit-method/blob/main/accessibility/README.md) that treats accessibility as part of every test pass
- [wp-dev-prompts](https://github.com/courtneyr-dev/wp-dev-prompts): [accessibility specialist agent prompts](https://github.com/courtneyr-dev/wp-dev-prompts/tree/main/agents/specialists/accessibility)
- [docs-and-release-rig](https://github.com/courtneyr-dev/docs-and-release-rig) and [agent-skills](https://github.com/courtneyr-dev/agent-skills)

Thanks to Amber Hinds and the Equalize Digital team for the work on [issue 1740](https://github.com/equalizedigital/accessibility-checker/issues/1740), and to Joe Dolson for Able Player.

Also on:- [ Mastodon](https://m.courtneyr.co/@courtneyr/117474020981491383)





[](https://courtneyr.dev/2026/10/20/wordpress-accessibility-remediation-claude-checker-pro/)Courtney RobertsonWordPress accessibility remediation: Claude and Checker Pro