AI Accessibility Checker: Tự Động Audit A11y Với Claude API
AAdmin
7 tháng 3, 2026
7 phút đọc
0 lượt xem
Xây dựng AI-powered accessibility checker tự động phân tích components và đề xuất fixes. Kết hợp axe-core với Claude để audit toàn diện hơn.
axe-core và Lighthouse bắt được ~30-40% accessibility issues. Phần còn lại — semantic meaning, cognitive load, context-dependent ARIA — cần human review. Hoặc... AI review. Claude excels tại việc hiểu context và semantics, khiến nó trở thành perfect companion cho automated a11y testing.
Approach: Hybrid Audit
Kết hợp 2 layers:
- axe-core: Bắt technical violations (missing alt, low contrast, missing labels)
- Claude AI: Analyze semantic issues (confusing tab order, misleading labels, poor error messages)
Implementation: A11y Audit API
// app/api/audit-a11y/route.ts
import Anthropic from "@anthropic-ai/sdk";
const claude = new Anthropic();
interface AuditRequest {
html: string;
componentName: string;
axeResults?: Record<string, unknown>[];
}
export async function POST(req: Request) {
const { html, componentName, axeResults }: AuditRequest = await req.json();
const axeSummary = axeResults?.length
? `
axe-core đã phát hiện: ${JSON.stringify(axeResults, null, 2)}`
: "
axe-core không phát hiện vi phạm kỹ thuật.";
const message = await claude.messages.create({
model: "claude-sonnet-4-6-20250514",
max_tokens: 2000,
messages: [{
role: "user",
content: `Audit accessibility cho component "${componentName}".
HTML:
```html
${html}
```
${axeSummary}
Phân tích và trả về JSON với format:
{
"score": number (0-100),
"issues": [{ "severity": "critical|major|minor", "element": string, "issue": string, "fix": string }],
"positives": [string],
"summary": string
}
Focus vào:
1. Keyboard navigation flow
2. Screen reader experience
3. ARIA usage correctness
4. Color/contrast (nếu có inline styles)
5. Focus management
6. Error state accessibility
7. Touch target sizes`,
}],
});
const content = message.content[0].type === "text" ? message.content[0].text : "";
const jsonMatch = content.match(/{[sS]*}/);
const audit = jsonMatch ? JSON.parse(jsonMatch[0]) : null;
return Response.json(audit);
}
Frontend: Audit Dashboard Component
// components/a11y-audit.tsx
"use client";
import { useState } from "react";
import { AlertTriangle, CheckCircle, XCircle, Info } from "lucide-react";
interface AuditIssue {
severity: "critical" | "major" | "minor";
element: string;
issue: string;
fix: string;
}
interface AuditResult {
score: number;
issues: AuditIssue[];
positives: string[];
summary: string;
}
const severityConfig = {
critical: { icon: XCircle, color: "text-red-600", bg: "bg-red-50" },
major: { icon: AlertTriangle, color: "text-orange-600", bg: "bg-orange-50" },
minor: { icon: Info, color: "text-blue-600", bg: "bg-blue-50" },
};
export function A11yAudit() {
const [html, setHtml] = useState("");
const [result, setResult] = useState<AuditResult | null>(null);
const [loading, setLoading] = useState(false);
async function runAudit() {
setLoading(true);
const res = await fetch("/api/audit-a11y", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ html, componentName: "UserComponent" }),
});
setResult(await res.json());
setLoading(false);
}
return (
<div className="space-y-6">
<textarea
value={html}
onChange={(e) => setHtml(e.target.value)}
placeholder="Paste component HTML here..."
className="w-full h-40 p-4 font-mono text-sm border rounded-lg"
/>
<button onClick={runAudit} disabled={loading}
className="px-6 py-2 bg-green-600 text-white rounded-lg">
{loading ? "Auditing..." : "Run A11y Audit"}
</button>
{result && (
<div className="space-y-4">
<div className="text-center">
<span className="text-4xl font-bold">{result.score}</span>
<span className="text-gray-500">/100</span>
</div>
{result.issues.map((issue, i) => {
const config = severityConfig[issue.severity];
return (
<div key={i} className={"p-4 rounded-lg " + config.bg}>
<p className={"font-medium " + config.color}>{issue.issue}</p>
<p className="text-sm mt-1">Fix: {issue.fix}</p>
</div>
);
})}
</div>
)}
</div>
);
}
Integration vào CI/CD
- Run audit trong Storybook — iterate qua tất cả stories và audit mỗi component
- Set threshold (vd: score >= 80) làm quality gate trong PR checks
- Cache audit results để avoid redundant API calls cho unchanged components
Limitations
- AI không thể test actual keyboard navigation — vẫn cần manual testing
- Visual issues (color contrast) cần rendered output, không chỉ HTML
- Cost: ~/bin/bash.01-0.03 per component audit — reasonable cho CI nhưng cần budgeting
AI a11y audit không thay thế manual testing, nhưng nó catches issues mà automated tools miss. Treat nó như extra safety net trong development workflow.
A
Admin
Bình luận (0)
Đăng nhập để bình luận
Chưa có bình luận nào. Hãy là người đầu tiên!