WP Manifestindependent plugin directory
manifest / utilities / wowstudio-accessibility-remediation

WOWStudio Accessibility Remediation

Free WordPress accessibility plugin that finds, fixes and documents WCAG 2.2 issues in your own code. Reviewable fixes, no overlay, no outbound requests.

by WOWStudio · github.com/wowstudio-dev/wowstudio-accessibility-remediation · website

★ 0stars
0forks

Install

No release zip yet. The repository archive installs, but the folder name will carry the branch suffix and updates will not flow:

wp plugin install https://github.com/wowstudio-dev/wowstudio-accessibility-remediation/archive/refs/heads/main.zip

A free WordPress accessibility plugin that finds, fixes and documents WCAG 2.2 issues in your own code. It scans every page twice, shows each finding on a live preview of the real page, and proposes changes to your markup and your own CSS that you review before anything is applied.

It is not an overlay. No widget or toolbar is ever added to your front end for your visitors — a site-wide fix enqueues only the CSS or the small script that fix needs, and nothing at all is enqueued until you turn one on. It makes no outbound requests of any kind — no account, no API, no telemetry, no AI. It never tells anybody their site is compliant, because no tool can.

Install it from the WordPress Plugin Directory: https://wordpress.org/plugins/wowstudio-accessibility-remediation/

See CLAUDE.md for the non-negotiable product rules and SPEC.md for the full feature specification and build order.

Status

Published. Version 1.0.4 is live in the WordPress Plugin Directory: https://wordpress.org/plugins/wowstudio-accessibility-remediation/

This repository is the source for what ships there, including the JavaScript and CSS that are distributed compiled. Keeping it public and current is a condition of staying in the directory, not a courtesy.

One plugin, free, nothing gated. There is no licensing SDK, no paid tier and no AI; see CHANGELOG.md for what went and why.

What works: the two-pass scanner (41 checks — 36 on the server, 5 in the browser), the inspector with a live page preview, the block-editor panel, deterministic CSS fixes with preview and undo, fourteen site-wide fixes, site-wide scanning, the bulk alt-text editor, theme triage, the overview dashboard, the accessibility-statement generator, reading-level checking with a plain-language summary, WP-CLI, and the admin columns.

What is next: three checks the current passes cannot reach — text too small to read, carousels, and animated GIFs — testing against the page builders beyond Elementor, and a screen-reader pass over the admin with disabled users, which is the largest gap and the one no tooling closes. CLAUDE.md has the list.

How it compares

Honest version, because you should pick the right tool rather than this one.

Equalize Digital Accessibility Checker (10,000 installs, 98/100 from 78 ratings) is the mature choice and checks a page as you edit it. Use it if you want a long track record and a company behind it with paid support. Its full-site scanning, its reports and its centralised results are part of the paid tier; ours are not, which is the main practical difference.

WP Accessibility by Joe Dolson (60,000 installs, 96/100 from 68 ratings) patches common theme faults automatically and is written by a W3C contributor. It is not a scanner and does not try to be. If all you want is the automatic markup patches and no findings UI, it is lighter than this and you should use it. Several of our fourteen site-wide fixes cover the same ground.

Overlay widgets — accessiBe, UserWay, AudioEye and the rest — inject a script that layers controls over your site and change nothing underneath. They are rejected by the disabled people they claim to serve, and one of them has drawn an FTC action over its marketing. Do not use one. The Overlay Fact Sheet, signed by several hundred accessibility practitioners, sets out why at length.

Use this one if you want the whole site scanned without paying, fixes that are written into your own code and survive the plugin being uninstalled, and a tool that reports what it cannot detect as plainly as what it can.

Nothing here replaces a person. Automated testing reaches part of WCAG and no more. Meaningful sequence, focus order and whether your alt text is actually accurate all need a human being, and testing with disabled users is not optional.

Requirements

Dependency Version
WordPress 6.8+
PHP 8.1+
Node 20+
Docker required for wp-env and for running the PHP test suite locally

Setup

composer install
npm install
npx wp-env start      # WordPress at http://localhost:8888 (admin/password)

Quality gate

Run the whole gate before every commit:

composer lint

That runs, in order:

Command Checks
composer phpcs WordPress-Core, -Extra, -Docs, PHPCompatibilityWP
composer phpstan Static analysis at level 6
composer test PHPUnit unit suite
composer check-claims No unqualified compliance claims anywhere
composer check-disclosures Every honesty caveat is still on the screen that needs it
npm run plugin-check The official WordPress Plugin Check

composer lint is the PHP half. The full 10-step gate — which also covers the JavaScript, the stylesheet, the colour contrast of our own interface, and the staleness of the translation template — is:

bash bin/gate.sh

npm run plugin-check and npm run makepot both build the plugin into dist/ first and run against that, not the working tree — otherwise they see node_modules, dev Composer dependencies, and tooling config as shipped files. See docs/RELEASE-CHECKLIST.md for what a release run involves.

Build the release artifact with:

npm run dist:zip

One build

There is one build and no flavours. bin/build.sh produces exactly what ships, so what the gate tests is what a user installs — which was never quite true while a server-side stripper generated the real zip from a premium tree.

Running tests locally

Brain Monkey depends on Patchwork, which does not support PHP 8.5. If your host PHP is newer than 8.4, run the suite in a container:

docker run --rm -v "$PWD":/app -w /app php:8.1-cli php vendor/bin/phpunit

CI runs the suite on PHP 8.1, 8.2, 8.3, and 8.4.

Two guards worth knowing about

Both are wired into CI and both fail the build.

bin/check-claims.php enforces product rule #1 — assist, never guarantee. It fails on any unqualified use of "compliant", "certified", "guaranteed", "lawsuit", and similar, in any file that ships. A line is allowed only if it negates the claim, carries a wsak:claim-reviewed annotation, or appears verbatim in bin/claims-allowlist.txt.

bin/check-disclosures.php is the other half of the same idea. The claim guard stops the plugin saying something it must not; this stops it quietly dropping something it must say. Product rules 1, 4 and 5 all rest on ordinary strings — the caveat under the score, the coverage lede, the draft banner on an unsigned statement — and any of those can be deleted in a routine refactor without breaking a test. Each surface it names must keep carrying its disclosure; reword them freely, but update the pattern when you do.

Repository layout

wowstudio-accessibility-remediation.php   Bootstrap: headers, constants, requirement
                                  checks, lifecycle hooks including uninstall
src/Core/                         Orchestrator, activation, installation
src/Db/                           Schema, repositories, typed records
src/Scanner/                      Engine, rules, registry, page fetching
src/AltText/                      Finding images that have never been described
src/Jobs/                         Action Scheduler queue and the bulk-scan worker
src/Remediation/                  The override layer, diffing, and fix review
src/SiteFixes/                    Site-wide fixes and the registry they hang off
assets/front/                     The one front-end script, shipped unbundled
src/Conformance/                  The accessibility statement and its sign-off
src/Rest/                         REST controllers
assets/src/                       React admin app (source)
build/                            Compiled admin app (generated, not tracked)
src/Admin/                        Admin menu
src/Support/                      Shared helpers (capabilities)
bin/build.sh                      Release build (honours .distignore)
bin/gate.sh                       The 10-step quality gate
bin/                              Product guards
docs/RELEASE-CHECKLIST.md         What a human must verify before release

Scanning

POST /wp-json/wsak/v1/scan       { "post_id": 12 }  requires wsak_run_scan
GET  /wp-json/wsak/v1/scans/<id>                    requires wsak_view_reports
GET  /wp-json/wsak/v1/scannable                     requires wsak_view_reports
GET  /wp-json/wsak/v1/coverage                      requires wsak_view_reports
GET  /wp-json/wsak/v1/overview                      requires wsak_view_reports
GET  /wp-json/wsak/v1/media                         requires wsak_apply_fix
POST /wp-json/wsak/v1/media/alt                     requires wsak_apply_fix
GET  /wp-json/wsak/v1/site-fixes                    requires wsak_view_reports
POST /wp-json/wsak/v1/site-fixes/<id>               requires wsak_manage_settings
GET  /wp-json/wsak/v1/fixes/css                     requires wsak_apply_fix
POST /wp-json/wsak/v1/fixes/css                     requires wsak_apply_fix
DEL  /wp-json/wsak/v1/fixes/css/<issue_id>          requires wsak_apply_fix
POST /wp-json/wsak/v1/runs                          requires wsak_run_scan
GET  /wp-json/wsak/v1/runs/<id>                     requires wsak_view_reports
GET  /wp-json/wsak/v1/statement                     requires wsak_view_reports
POST /wp-json/wsak/v1/statement                     requires wsak_manage_settings
POST /wp-json/wsak/v1/statement/attest              requires wsak_manage_settings
POST /wp-json/wsak/v1/statement/withdraw            requires wsak_manage_settings

The accessibility statement

Published with the wowstudio/accessibility-statement block or the [wsak_accessibility_statement] shortcode. Both render on the server from current settings, so what visitors see is always the statement as it now stands.

The plugin never claims conformance on anybody's behalf: every conformance sentence is attributed to the organisation by name. An unsigned statement publishes as a draft saying nobody has checked it, sign-off is refused while the statement is unfinished, and editing it withdraws the sign-off so an approved statement cannot come to say something its approver never read.

The override layer

Applying a fix never edits post content. The correction is stored as a pair of "this element" and "this element instead", and substituted as the page renders. Undo is a flag, not a restore, and deactivating the plugin returns every page to its original markup by ceasing to filter.

Matching happens in the DOM, not on the string, because a scan reads the rendered page — where WordPress has already added attributes like decoding="async" — while the override runs over post content, which has fewer. See Remediation\Substitution for the direction that tolerance runs in and why.

No route returns an API key. Reading the AI settings tells you whether a key is stored and shows a masked hint; that is the most any caller can learn.

The scanner fetches the whole rendered page over a loopback request, because the page language, title, landmarks, and most of the theme's markup live outside post content. If loopback requests are blocked — many hosts block them, and wp-env cannot reach its own mapped port — the scan falls back to post content alone and labels the result: full_page is false and coverage_notice explains what was skipped. The document-level rules recognise a fragment and stay quiet, so a reduced scan under-reports rather than inventing failures.

To supply markup yourself, or to test the full-page path locally, filter wsak_page_html. To make a loopback failure a hard error instead, return false from wsak_allow_content_fallback.

The admin app

Source lives in assets/src, compiled output in build/. build/ is generated and not tracked, so bin/build.sh compiles it and refuses to produce a release without it.

npm run start     # watch
npm run build     # one-off

Translatable strings live in the JavaScript sources, not the minified bundle, so npm run makepot copies assets/src into the build for the duration of the extraction. Running wp i18n make-pot against the build alone silently drops every string in the app.

License

GPL-2.0-or-later. See LICENSE.