Phantom
Accessibility Transformer — Chrome Extension
A production-grade Chrome Extension that scans any live website for WCAG 2.2 violations, highlights broken elements directly on the page, generates AI-powered code fixes, and exports professional PDF audit reports in one click.
The Problem
1.3 billion people worldwide live with some form of disability. When they visit a website, there is a 97% chance it will fail basic accessibility standards — broken form labels, missing image descriptions, unreadable color contrast, keyboard traps.
Every existing tool — Lighthouse, axe DevTools, WAVE — does the same thing: it generates a report. It tells you what is broken. Then it leaves you alone to figure out the rest. A developer reads the report, tries to find the broken element in thousands of lines of HTML, researches what the WCAG rule means, attempts a fix, and re-runs the audit. This process takes hours. Most companies never get around to it.
The question I asked: what if the tool did not just report the problem — but showed you exactly where it was, extracted the broken code, and wrote the fix for you?
Technical Challenges
Scanning a live page from inside a popup
A Chrome Extension popup is an isolated HTML page. It cannot directly read or modify the tab the user is browsing. The only bridge is Chrome's scripting API.
The naive approach — injecting axe-core as a script tag — fails in Manifest V3 because of the new Content Security Policy restrictions. The solution was a two-step injection pattern:
Step 1 → chrome.scripting.executeScript({ files: ['axe.min.js'] })
Injects axe-core as a web-accessible resource into the live page
Step 2 → chrome.scripting.executeScript({ func: runAxeScan })
Now that axe exists on the page, runs axe.run() and returns resultsThis two-call pattern is not documented anywhere — I discovered it through reading Chrome Extension source code and Manifest V3 migration guides. It is the only reliable way to run a WebAssembly-powered accessibility engine inside a foreign page from an extension popup.
The live element highlighter
After scanning, every broken element on the live page should glow red — without breaking the page, without affecting the site's own CSS, and without persisting after the user clears highlights. Three problems had to be solved:
Z-index conflicts. Many sites use z-index: 9999 on overlays and modals. The solution was to use outline with outline-offset rather than border or box-shadow — outlines render outside the element's box model and are not affected by child stacking contexts.
Cleaning up. Every injected style was scoped to a unique id (phantom-styles) so a single element.remove() call cleans everything up with no leftover CSS or data attributes.
Hover tooltips without a React tree. The tooltip had to work inside the live page — not the popup. This meant pure DOM manipulation: a CSS ::after pseudo-element reading from a data-phantom-label attribute set per element. No React, no event listeners, no memory leaks.
Extracting live HTML for the fix engine
The fix engine needed the actual broken HTML from the page — not a generic example. axe-core returns a CSS selector for each violation. Using that selector to extract the outerHTML required another chrome.scripting.executeScript call with the selector passed as an argument.
The key constraint: chrome.scripting functions run in a completely isolated context — they cannot close over variables from the popup. Everything must be passed explicitly as args. This forced a clean separation between popup state and page-side execution that made the code more reliable overall.
Client-side PDF generation
The PDF report needed to look professional — dark cover page, score ring, severity badges, paginated violation list, page numbers. jsPDF provides a low-level drawing API (think Canvas, not HTML) — every element is positioned with explicit coordinates.
The score ring required manual arc math:
circumference = 2π × radius
arc_length = circumference × (score / 100)
strokeDashoffset = circumference - arc_lengthPage breaks required tracking the current Y position and triggering a new page when the remaining space was insufficient for the next violation card.
Architecture Decisions
Lighthouse requires a full page reload and runs in a separate DevTools process. It cannot scan a page being actively browsed. axe-core runs as a JavaScript library directly inside the page's DOM — it sees exactly what the user sees, including dynamically rendered content and SPA state.
Google deprecated Manifest V2 in 2024. V3's service worker model forced a cleaner architecture — instead of a persistent background page with direct DOM access, all page interaction is explicit and auditable through chrome.scripting.
Zero backend infrastructure. Zero user data leaving the machine. Instant downloads. The tradeoff is bundle size — jsPDF adds approximately 200KB. For an extension popup that loads on demand this is acceptable.
chrome.storage.local is shared across all extension contexts — popup, background worker, content scripts. It persists across extension updates and browser restarts. localStorage is scoped to a single page origin and is destroyed when the popup closes.
What I Learned
The browser is a platform, not just a runtime. Building Phantom required understanding how Chrome manages isolated execution contexts, how the scripting API bridges them, and how CSS rendering works at the level of stacking contexts and box models. This is a different category of knowledge from building React applications.
Constraints produce better architecture. The Manifest V3 restriction on background page access forced every page interaction through a single, explicit API. The resulting code is easier to reason about than the V2 equivalent would have been.
The demo is the product. The most important engineering decision was choosing the live element highlighter as a feature. A popup showing a list of issues is forgettable. A tool that draws red borders on a real website while you watch — that is memorable. When I demo Phantom, people understand the problem and the solution in under 10 seconds.
Results
What's Next
Multi-page site audit — crawl an entire domain and aggregate scores across pages.
Score trend chart — visualize accessibility improvement over time using D3.
Real AI integration — replace smart mock fixes with actual AI-generated fixes using the Anthropic API.