无障碍自动化与组件审计
版本: 0.2.0 · 类型: ✨ 新功能
规格:
.specs/2026-06-16/a11y-automation/Linear:父 DES-117;本批改动归 Phase 1/3/4(DES-118 / DES-120 / DES-121)
背景
把 @plaud/design 的无障碍从「依赖 Radix 兜底 + 人工 review」升级为「可自动验证(axe)+ 量化标准(WCAG 2.1 AA)+ 存量已审计修复」。
工具链 / 基建(Phase 1,无组件行为变化)
package.json:devDependencies 增vitest-axe@0.1.0、@axe-core/playwright@4.11.3、axe-core@4.12.1;新增test:a11y、a11y:audit脚本- 新增
src/test/axe-config.ts:共享WCAG_AA_TAGS/AXE_RUN_OPTIONS,vitest / Playwright / 审计脚本三处共用 test/setup.ts:注册 vitest-axe matcher;类型增强见同目录test/vitest-axe.d.tsvitest.config.ts/tsconfig.json/tsconfig.build.json:exclude增浏览器测试 globsrc/**/*.ct.spec.tsx- 浏览器测试统一
playwright-ct.config.ts(testMatch*.ct.spec.tsx),test:visual/test:a11y按@visual/@a11ytag 区分 - 新增
playwright/axe-utils.ts的analyzeA11y - PoC:Button / Dialog / Input 的单测
describe('accessibility')追加 axe 断言 + 同目录*.ct.spec.tsx的@a11y块 eslint-plugin-jsx-a11y确认已在eslint.config.js生效
存量审计(Phase 3,离线,不动 test:run)
- 新增
scripts/a11y/a11y-audit-config.ts(49 公开组件 → render fixture)、scripts/a11y/a11y-audit.ts(happy-dom + axe 批量跑)、scripts/a11y/a11y-dom-setup.ts - 全量审计:49 组件全部渲染成功,47 合规 / 2 待修复,报告产出到
a11y-report/(gitignore)
组件修复(Phase 4)
Slider — thumb 缺可访问名(aria-input-field-name,serious)
问题:aria-label 被透传到 Radix Slider 的 root(roleless),而非 role="slider" 的 Thumb,导致 thumb 无可访问名(WCAG 4.1.2)。
改动文件:src/components/ui/slider.tsx、src/components/Slider/Slider.test.tsx、新增 src/components/Slider/Slider.ct.spec.tsx(@a11y)
改动内容:ui/slider 解构 aria-label / aria-labelledby 并落到 SliderPrimitive.Thumb;更新两条原断言(root → thumb),新增 axe 断言。
Menu — menuitem 缺合规父级(aria-required-parent,critical)
问题:<nav> 内的 role="menuitem" 没有 role="menu"/group 父级。
改动文件:src/components/Menu/Menu.tsx、src/components/Menu/Menu.test.tsx、新增 src/components/Menu/Menu.ct.spec.tsx(@a11y)
改动内容:
- 顶层 item 容器加
role="menu"+aria-orientation="vertical" - 子菜单父项加
aria-haspopup="menu" MenuSubItem折叠容器加role="none"(让父 menu 透过它),其折叠内容容器与MenuGroup折叠内容容器加role="group"(满足aria-required-children,并为内部 menuitem 提供合规父级)- 新增 axe 断言(含嵌套子菜单)
文档(Phase 2)
docs/constitution.md「无障碍」章节锚定 WCAG 2.1 AA + 三层自动化基线CLAUDE.md§9.2 补 a11y 自动化测试约定- 新增
docs/a11y-checklist.md(按组件类别的验收清单),并入docs/README.md索引
测试文件布局(最终约定,无组件行为变化)
为降低每组件文件数与目录散落,本批同时把测试文件归位(既有的 *.visual.spec.tsx 一并迁移):
- 浏览器测试单文件:每组件浏览器测试统一
[Name].ct.spec.tsx,内部test.describe(..., { tag: '@visual' | '@a11y' }, …)分块(共 11 个组件,原 8 个 visual + 5 个 a11y 去重)。单测*.test.tsx(vitest / happy-dom)保持独立(跨 runner 不可合并)。 - 单一 Playwright 配置:
playwright-ct.config.ts(testMatch*.ct.spec.tsx);test:visual=--grep @visual、test:a11y=--grep @a11y。 - scripts 分域:
scripts/visual/(visual-diff-config / generate-visual-gallery / ai-visual-review / api)、scripts/a11y/(a11y-audit / a11y-audit-config / a11y-dom-setup)。 - 测试支撑移出 src:
src/test/→ 顶层test/(src/回归纯生产源码);两个图标 registry 改为自解释命名unit-icon-registry.tsx(单测 stub)/ct-icon-registry.tsx(CT 真实 Figma)。连带:vitest.configsetupFiles、tsconfig.jsoninclude 增test、tsconfig.build去掉已失效的src/testexclude、eslint.configstrict 块纳入test/**、各 importer 相对路径。 - 按 runner 归属再分:
ct-icon-registry.tsx(仅playwright/index.tsx消费)移入playwright/,与 CT harness 同归;test/只保留 vitest 全局 + 跨 runner 共享的setup.ts/unit-icon-registry.tsx/axe-config.ts(这三者被单测 / CT / 审计脚本多方引用,属中立共享,不归任一 runner 目录)。playwright/整目录不参与 tsc/eslint(引@playwright/test),故 ct-icon-registry 移入后由 CT 构建期转译、不再单独类型检查。 - 测试一律进
__tests__/:全部 74 个*.test.tsx/*.ct.spec.tsx/*Fixture.visual.tsx从组件根目录迁入各自的__tests__/(测试与源码彻底分离),相对 import 相应 +1 层。__ai-review__/截图产物仍留组件根(visual:gallery路径不变,ct.spec 的 REVIEW_DIR 上移一层)。vitest / tsconfig / eslint 的src/**glob 递归,自动覆盖__tests__/,无需改门禁规则。 - 布局速查表见
CLAUDE.md§9。