Skip to content

DOCS

Pipeline

01
Submit URL
02
ScanAST + crawler
03
PrioritizeAI grouping
04
Generate fixesAI diffs
05
Create PRsGitHub API

Sequence

Client
POST /api/scan{ repoUrl }
POST /api/prioritize{ violations[] }
POST /api/pr{ repoUrl, group } (loop)
POST /api/scan
GitHub APIGET /repos/{o}/{r}/git/trees
GitHub APIGET file contents
run WCAG checks
Client{ violations[], score }
GitHub API
POST /api/scanfile tree
POST /api/scansource files
POST /api/prPR url
POST /api/prioritize
OpenAI APIgroup + rank
Client{ groups[] }
OpenAI API
POST /api/prioritizeFixGroup[]
POST /api/prpatched files
POST /api/pr
OpenAI APIgenerate diffs
GitHub APIcreateBlob + commit + PR
Client{ url, number }

Quick Start

git clone https://github.com/yourusername/a11y-forge
cd a11y-forge
cp .env.local.example .env.local
# Edit .env.local with your keys
npm install
npm run dev

Open http://localhost:3000, paste a GitHub URL, click Scan.

API

POST
/api/scan

Walk repo tree, download up to 150 files, run all WCAG checks.

POST
/api/prioritize

Group violations by category, rank by impact. Requires consentToAi.

POST
/api/pr

Generate diffs, commit, open PR. dryRun returns diffs only.

POST
/api/report

Render HTML report. Returns text/html attachment.

POST
/api/report/pdf

Render PDF report via pdf-lib. Requires score.

GET
/api/badge

SVG score badge. POST with score for custom, GET for default.

API Examples

# POST /api/scan
curl -X POST https://a11y-forge.vercel.app/api/scan \
  -H "Content-Type: application/json" \
  -d '{"repoUrl": "https://github.com/owner/repo"}'
# POST /api/prioritize
curl -X POST https://a11y-forge.vercel.app/api/prioritize \
  -H "Content-Type: application/json" \
  -d '{"violations": [...], "consentToAi": true}'
# POST /api/pr
curl -X POST https://a11y-forge.vercel.app/api/pr \
  -H "Content-Type: application/json" \
  -d '{"repoUrl": "https://github.com/owner/repo", "group": {...}}'
# POST /api/report
curl -X POST https://a11y-forge.vercel.app/api/report \
  -H "Content-Type: application/json" \
  -d '{"violations": [...], "score": 42}'
# POST /api/badge (custom)
curl -X POST https://a11y-forge.vercel.app/api/badge \
  -H "Content-Type: application/json" \
  -d '{"score": 85}'

# GET /api/badge (default)
curl https://a11y-forge.vercel.app/api/badge

Example scan response:

{
  "violations": [
    { "type": "missing-alt-text", "count": 3, "severity": "error" },
    { "type": "missing-form-label", "count": 1, "severity": "error" }
  ],
  "score": 42,
  "screenReader": "4 violations found across 2 categories"
}

Scanners

scanner.ts (html) — missing-alt-text: images missing alt attribute

scanner.ts (html) — missing-aria-label: buttons without text or aria-label

scanner.ts (html) — missing-form-label: inputs without accessible label

scanner.ts (html) — missing-html-lang: <html> missing lang attribute

contrast.ts — CSS color contrast ratio (4.5:1 AA, 7:1 AAA)

keyboard.ts — keyboard traps, tabindex ≥0, missing escape

headings.ts — heading hierarchy, skips, empty, single h1

links.ts — vague text, empty hrefs, missing names

ast-scanner.ts — Babel JSX/TSX AST walker for iframe title, JSX-specific patterns

screen-reader.ts — simulate SR output before/after per violation

confidence.ts — heuristic detection confidence 0-100%

violation-meta.ts — severity, WCAG ref, fix strategy per type

Setup

GITHUB_TOKEN=ghp_...     # required, repo scope
OPENAI_API_KEY=sk-...    # optional, enables AI grouping

npm install
npm run dev
npm test

Limitations

• File limit: 150 files per scan
• Rate limit: 20 req/min per IP
• Body limit: 500KB
• Scope-limited checks — contrast, keyboard traps are heuristic, no runtime pixel verification
• Public repos only
• Static analysis only — no live browser rendering