跳到主要内容

无障碍自动化与组件审计

版本: 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.3axe-core@4.12.1;新增 test:a11ya11y:audit 脚本
  • 新增 src/test/axe-config.ts:共享 WCAG_AA_TAGS / AXE_RUN_OPTIONS,vitest / Playwright / 审计脚本三处共用
  • test/setup.ts:注册 vitest-axe matcher;类型增强见同目录 test/vitest-axe.d.ts
  • vitest.config.ts / tsconfig.json / tsconfig.build.jsonexclude 增浏览器测试 glob src/**/*.ct.spec.tsx
  • 浏览器测试统一 playwright-ct.config.ts(testMatch *.ct.spec.tsx),test:visual / test:a11y@visual / @a11y tag 区分
  • 新增 playwright/axe-utils.tsanalyzeA11y
  • 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.tsxsrc/components/Slider/Slider.test.tsx、新增 src/components/Slider/Slider.ct.spec.tsx@a11y

改动内容ui/slider 解构 aria-label / aria-labelledby 并落到 SliderPrimitive.Thumb;更新两条原断言(root → thumb),新增 axe 断言。

问题<nav> 内的 role="menuitem" 没有 role="menu"/group 父级。

改动文件src/components/Menu/Menu.tsxsrc/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 @visualtest: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)。
  • 测试支撑移出 srcsrc/test/ → 顶层 test/src/ 回归纯生产源码);两个图标 registry 改为自解释命名 unit-icon-registry.tsx(单测 stub)/ ct-icon-registry.tsx(CT 真实 Figma)。连带:vitest.config setupFiles、tsconfig.json include 增 testtsconfig.build 去掉已失效的 src/test exclude、eslint.config strict 块纳入 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。