DEV Community

Cover image for What If Your A11y Linter Could Actually Fix the Bugs It Found?
Muhammed Safvan
Muhammed Safvan

Posted on

What If Your A11y Linter Could Actually Fix the Bugs It Found?

GitHub Copilot CLI Challenge Submission

This is a submission for the GitHub Copilot CLI Challenge

What I Built

What if your linter could actually fix the problems it found?

That's a11y-pilot - a CLI that scans your frontend code for accessibility violations, then spawns GitHub Copilot CLI to fix each one. Not "here's a suggestion". it literally invokes copilot --prompt with a crafted fix instruction and lets the AI refactor your code in place.

I pointed it at a file with 12 accessibility issues. Ran a11y-pilot fix. Every single issue was resolved — <div onClick> became <button>, empty <img> got meaningful alt text inferred from the filename, unlabeled inputs got proper aria-label. Re-scanning the file: 0 issues. No manual edits.

Demo


npm: https://www.npmjs.com/package/a11y-pilot
GitHub: https://github.com/Safvan-tsy/a11y-pilot

How it works

Scan → Detect → Prompt Engineer → Copilot CLI Fixes → Done
Enter fullscreen mode Exit fullscreen mode
  1. Scan - Point it at any directory or file. It walks your project and parses JSX, TSX, HTML, Vue, Svelte, and Astro files using Babel AST (for JSX/TSX) and htmlparser2 (for HTML-like templates).

  2. Detect - 15 accessibility rules run against every parsed element, checking for WCAG violations across 5 categories.

  3. Auto-fix - For each issue, a11y-pilot builds a precise, context-rich prompt and spawns copilot --prompt <text> --allow-all-tools. Copilot CLI reads the file, understands the surrounding code, and applies the minimum diff needed. No blind find-and-replace actual AI-driven refactoring.

final status image

The Copilot CLI bridge — the core of the project

This is not just a scanner that suggests fixes. The entire point is that Copilot CLI is the execution engine. When you run a11y-pilot fix ./src, here's what happens under the hood:

┌─────────────────┐     ┌──────────────────┐     ┌─────────────────┐
│   a11y-pilot    │────▶│  Issue detected   │────▶│  Build prompt   │
│   scanner       │     │  (rule engine)    │     │  (context-rich) │
└─────────────────┘     └──────────────────┘     └────────┬────────┘
                                                          │
                                                          ▼
┌─────────────────┐     ┌──────────────────┐     ┌─────────────────┐
│   File fixed!   │◀────│  Copilot applies  │◀────│  copilot CLI    │
│   ✔ Report      │     │  the fix          │     │  invoked        │
└─────────────────┘     └──────────────────┘     └─────────────────┘
Enter fullscreen mode Exit fullscreen mode

Each rule carries a copilotPrompt field — a carefully crafted instruction that tells Copilot CLI exactly what's wrong and how to fix it. For example, the no-div-button rule generates prompts like:

"In file src/Hero.tsx at line 20, fix this accessibility issue: <div> has a click handler but is missing role and tabIndex. Replace this <div> element with a native <button> element. Move the onClick handler to the button. Remove any cursor: pointer styling (buttons have it by default). Only modify the minimum code necessary."

Copilot CLI then reads the full file context, understands the surrounding JSX structure, and makes intelligent fixes — not just string replacements, but real refactoring. It adds meaningful alt text based on image filenames, converts <div onClick> to proper <button> elements, wraps navigation links in <nav> with appropriate aria-label, and more.

Category Dashboard

After scanning, a11y-pilot renders a visual breakdown dashboard right in the terminal:

  ✖ Found 46 issues (35 errors, 11 warnings) in 4 files (5 scanned)

  📊 Issue Breakdown
  ──────────────────────────────────────────────────
   ♿ Accessibility       ████████░░░░░░░░░░░░   19 (41%)  19E
   🏗️ Semantic HTML      █████░░░░░░░░░░░░░░░   11 (24%)  4E 7W
   ⌨️ Keyboard            ███░░░░░░░░░░░░░░░░░    7 (15%)  5E 2W
   🏷️ ARIA               ██░░░░░░░░░░░░░░░░░░    5 (11%)  5E
   👆 Interaction         ██░░░░░░░░░░░░░░░░░░    4 (9%)   2E 2W
  ──────────────────────────────────────────────────
  14 rules triggered: img-alt, form-label, no-div-button, ...
Enter fullscreen mode Exit fullscreen mode

This gives you an instant snapshot of where your accessibility debt lives — is it ARIA misuse? Keyboard traps? Missing semantics? You know exactly where to focus.

My Experience with GitHub Copilot CLI

GitHub Copilot CLI was central to this project in two distinct ways:

1. Copilot CLI as the product's engine

The marquee feature of a11y-pilot is --auto-fix. When triggered, it spawns copilot --prompt <text> --allow-all-tools for each detected issue. I discovered the --prompt flag enables non-interactive mode, and --allow-all-tools auto-approves tool use — this combination is what makes programmatic Copilot CLI invocation possible.

The quality of fixes was surprisingly excellent. Given a file with <div onClick={() => alert('clicked')}>, Copilot CLI didn't just slap a role="button" on it — it actually converted the entire element to a <button>, moved the handler, and preserved the className. For empty <a href="/profile"></a>, it added aria-label="Profile" — inferring the label from the URL. These aren't template fixes; they're context-aware refactoring.

The key insight was prompt engineering per rule. Each of the 15 rules carries a copilotPrompt field — a precisely crafted instruction that gives Copilot CLI enough context to understand the problem and the expected fix pattern, without being so prescriptive that it loses the ability to adapt to surrounding code. This prompt design is what turns a11y-pilot from "accessibility linter" into "accessibility linter + AI-powered fixer."

2. Copilot CLI as a development tool

Beyond the product itself, Copilot CLI was my primary development companion throughout the build:

  • Parser logic — Writing Babel AST traversal for JSX elements with normalized attribute handling required getting a lot of edge cases right (spread props, expression containers, member expressions). Copilot CLI helped iterate on the visitor pattern and handle the @babel/traverse ESM default export quirk (_traverse.default || _traverse).

  • Rule implementation — For each of the 15 rules, I used Copilot CLI to reference WCAG criteria and ensure the check() functions handle edge cases correctly — like not flagging <a> tags without href in the aria-hidden-focus rule, or skipping input[type="hidden"] in form-label checks.

  • Debugging — When the copilot-bridge initially used shell: true and hit the Node.js DEP0190 deprecation warning, Copilot CLI helped me switch to direct binary resolution with execFileSync('which', ['copilot']) and proper spawn() without shell.

What impressed me

The thing that stood out most was Copilot CLI's ability to handle file-level context. When I pointed it at a file with 12 accessibility issues and said "fix the <div onClick> on line 20," it didn't break the other 11 problematic lines. It made surgical, minimal edits. That's the property that made the auto-fix feature viable — I could confidently fix issues one-by-one or in batches without worrying about cascading breakage.

Project links

Top comments (17)

Collapse
 
safvantsy profile image
Muhammed Safvan

For anyone curious about the Copilot CLI integration: the key discovery was copilot --prompt "..." --allow-all-tools. The --prompt flag runs it non-interactively, and --allow-all-tools auto-approves file edits. Without these two flags, programmatic invocation wouldn't be possible. Happy to share more about the prompt engineering approach if anyone's interested.

Collapse
 
btc_2829ba6504799d6802 profile image
syed Amjad

good work

Collapse
 
saho_unfold profile image
saho

good problem solver

Collapse
 
raneemkayakkal profile image
Raneem K

Great Work!!

Collapse
 
safvantsy profile image
Muhammed Safvan

thanks

Collapse
 
anjala_binthrafeeqk_682 profile image
Anjala Binth Rafeeq K

How is this different from eslint-plugin-jsx-a11y ?

Collapse
 
safvantsy profile image
Muhammed Safvan

eslint-plugin-jsx-a11y is excellent and covers similar ground for React projects.
key difference is:

  • a11y-pilot works across JSX, TSX, HTML, Vue, Svelte, and Astro from a single CLI,
  • the auto-fix actually invokes Copilot CLI to do intelligent refactoring rather than just suggesting what to change.

Think of it as "what if your linter had an AI pair programmer attached."

Collapse
 
saho_unfold profile image
saho

Great idea. productivity win 🙌

Collapse
 
safvantsy profile image
Muhammed Safvan

thanks

Collapse
 
austin_amento_860aebb9f55 profile image
austin amento

Very cool!

Collapse
 
nazeem_ck_c73d647e8752629 profile image
Nazeem Ck

Accessibility always matter. Great work 👏

Collapse
 
bhavin-allinonetools profile image
Bhavin Sheth

This is seriously impressive. I recently ran an accessibility scan on one of my tool pages and fixing issues manually took hours — especially replacing clickable divs and adding proper labels. The idea of auto-fix with context-aware refactoring is a huge time saver. Curious — did you notice any cases where Copilot’s fix needed manual correction, or was it reliable most of the time?

Collapse
 
gocareer_tech_a05cbbe4416 profile image
GoCareer tech

nice terminal ui

Collapse
 
safvantsy profile image
Muhammed Safvan

Thanks, the terminal output uses chalk, gradient-string, and boxen.

Collapse
 
hello_aura_02a4b07231b30f profile image
Hello Aura

good work safvan

Collapse
 
arjun_prakash_d38f4f3e375 profile image
Arjun Prakash

This is seriously impressive 👏🔥