当可访问性审计遇上 AI:设计系统 WCAG 合规的自动化检查流水线
当可访问性审计遇上 AI:设计系统 WCAG 合规的自动化检查流水线
一、深度引言与场景痛点
WCAG 合规检查是设计系统的法律审计——每条规范(对比度 4.5:1、焦点可见、文字可读、交互可达)都需要逐一验证。手动审计覆盖 80 个组件的合规性需要 2-3 天,而且人眼对对比度的判断误差约 15%(视觉上认为够亮的文字,实际对比度可能只有 3.8:1)。
AI 辅助可访问性审计把手动逐一检查变为自动批量扫描——代码扫描检测 ARIA 属性缺失,算法计算对比度比率,大模型推断交互可达性问题,三路扫描覆盖 WCAG 的三类合规要求。
二、底层机制与原理深度剖析
flowchart TD A[设计系统组件库] --> B[WCAG 三类合规扫描] B --> B1[可感知性:对比度 + 文字可读性"] B --> B2[可操作性:键盘可达 + 焦点可见"] B --> B3[可理解性:ARIA 标签 + 错误提示"] B1 --> C1[算法精确计算:对比度比率 ≤ 4.5:1 报警"] B2 --> C2[代码扫描检测:缺失 ARIA / tabindex"] B3 --> C3[AI 语义推断:错误提示是否可理解"] C1 & C2 & C3 --> D[合规报告输出] D --> D1[严重违规:对比度不足 / ARIA 缺失"] D --> D2[中等违规:焦点不可见 / 提示不明确"] D --> D3[合规率统计:80个组件的合规百分比"]三、生产级代码实现与最佳实践
// scripts/a11y-audit/wcag-audit.ts import fs from 'fs'; import { OpenAI } from 'openai'; interface WCAGViolation { criterion: string; // WCAG 条目号,如 "1.4.3" severity: 'A' | 'AA' | 'AAA'; component: string; description: string; currentValue: string; requiredValue: string; fixSuggestion: string; } async function runWCAGAudit(dir: string): Promise<WCAGViolation[]> { const violations: WCAGViolation[] = []; // 1. 对比度计算:精确算法而非人眼判断 const cssFiles = fs.readdirSync(dir, { recursive: true }) .filter(f => f.endsWith('.css')); for (const file of cssFiles) { const css = fs.readFileSync(fs.join(dir, file), 'utf-8'); // 提取所有前景色和背景色对 const colorPairs = extractColorPairs(css); for (const pair of colorPairs) { const ratio = calculateContrastRatio(pair.foreground, pair.background); if (ratio < 4.5) { violations.push({ criterion: '1.4.3', severity: 'AA', component: file, description: `文字与背景对比度不足`, currentValue: `${ratio.toFixed(2)}:1`, requiredValue: '≥ 4.5:1', fixSuggestion: `调亮前景色或调暗背景色以满足对比度要求` }); } } } // 2. ARIA 属性扫描:代码扫描而非视觉判断 const tsxFiles = fs.readdirSync(dir, { recursive: true }) .filter(f => f.endsWith('.tsx')); for (const file of tsxFiles) { const code = fs.readFileSync(fs.join(dir, file), 'utf-8'); // 检查交互元素是否有 aria-label const buttonElements = code.match(/<button[^>]*>/g) || []; for (const btn of buttonElements) { if (!btn.includes('aria-label') && !btn.includes('aria-labelledby')) { // 检查按钮是否有文字内容 if (!btn.match(/<button[^>]*>[^<]+<\/button>/)) { violations.push({ criterion: '4.1.2', severity: 'AA', component: file, description: '图标按钮缺少 aria-label', currentValue: '无 aria-label', requiredValue: 'aria-label 属性', fixSuggestion: '添加 aria-label 描述按钮操作' }); } } } } // 3. AI 语义推断:错误提示的可理解性 const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY }); const errorMessages = code.match(/errorMessage.*['"](.+)['"]/g) || []; if (errorMessages.length > 0) { const response = await openai.chat.completions.create({ model: 'gpt-4o', messages: [ { role: 'system', content: '判断错误提示是否清晰可理解,是否提供修复方向。' }, { role: 'user', content: `错误提示列表:${errorMessages.join('\n')}` } ], response_format: { type: 'json_object' }, max_tokens: 2000 }); const result = JSON.parse(response.choices[0].message.content || '{}'); for (const item of (result.violations || [])) { violations.push({ criterion: '3.3.3', severity: 'AA', component: item.component || 'unknown', description: '错误提示不提供修复建议', currentValue: item.message, requiredValue: '错误提示应包含修复方向', fixSuggestion: item.suggestion || '添加修复建议文字' }); } } return violations; }四、边界分析与架构权衡
对比度计算与渲染偏差。算法计算的是 CSS 中声明的色值对比度,但浏览器渲染后的实际色值可能因字体渲染、抗锯齿、混合模式而偏差。算法结果与视觉感知的误差约 5%,需要人工确认边缘值(3.8-4.5 之间的对比度)。
ARIA 扫描的语法限制。正则匹配<button无法覆盖所有交互元素——动态生成的按钮、自定义组件的交互容器、第三方库的按钮。AST 解析比正则更精确但实现成本更高。
AI 语义推断的不确定性。大模型对"错误提示是否可理解"的判断可能不一致——同一个提示多次调用可能一次判定为"可理解"、一次判定为"不够清晰"。解决方案:只标注为"疑似不清晰",需要设计师确认。
五、总结
WCAG 合规的自动化审计把 2-3 天的手动检查变为几分钟的自动扫描——对比度用算法精确计算(不再依赖人眼判断),ARIA 属性用代码扫描检测(不再遗漏),错误提示用 AI 语义推断(不再只检查格式)。
三路扫描的分工原则是"确定性检查用算法,语义性检查用 AI"——对比度和 ARIA 缺失是确定性判断,交给算法精确执行;错误提示的可理解性是语义判断,交给大模型辅助推断。两类能力各司其职,不交叉不替代。
审计的终极目标是让合规成为 CI 的一部分——每次代码提交前自动扫描 WCAG 合规性,违规则阻断合并。合规不再是事后审计的补充,而是事前检查的基础设施,让设计系统的每一个组件在进入仓库时就带着合规合格证。