A11y.md:让无障碍规则嵌入开发流程的上下文系统

📅 2026/8/27 1:35:25
A11y.md:让无障碍规则嵌入开发流程的上下文系统
记得有一天组里的同事在评估一个无障碍缺陷弹窗关闭后键盘焦点没有回到触发按钮上。修复只需要两行代码真正让人沉默的问题是——写这个弹窗的开发者不知道要做焦点管理吗大概率知道。但在写那个组件的几十分钟里他脑子里没有“这是一个弹窗必须处理焦点移入和移出”这条上下文。这就是无障碍开发最大的困境规则不难难的是在写每一行代码时让相关规则“恰好出现在你眼前”。A11y.md 这个项目试图解决的就是这个上下文缺失问题。从项目定位来看它不是又一个自动修复全站无障碍问题的跑分工具也不是一份无人阅读的《无障碍开发规范》。它是一套以 Markdown 为载体的上下文系统把项目的无障碍基线、高风险组件、已知缺陷和验收清单变成开发流程中随时可见、工具可读、版本可追踪的一部分。这篇文章会拆解 A11y.md 的核心思路、适用场景然后给出一个可执行的落地方法如何为项目编写一份真正有用的 A11y.md并把它接入编码助手和 CI 验证闭环。1. 无障碍开发真正难在哪里规则不难上下文难先说一个容易被误解的事实绝大部分开发者对无障碍规则并不陌生。图片加alt、按钮加aria-label、颜色对比度达标、表单错误提示通过aria-describedby关联到输入框——这些知识点随便搜一下就能列出几十条。就算没系统学过 WCAG只要在真实项目里被 QA 打过几个无障碍缺陷也能把高频规则记住。但记住规则和能在正确的时候想起正确规则是两回事。以最常见的弹窗组件为例。一个合格的弹窗至少需要同时满足这些要求打开弹窗时焦点移入弹窗内部关闭弹窗或按 Esc 时焦点回到触发元素弹窗打开期间读屏软件不应读到背后的内容弹窗标题需要通过aria-labelledby关联如果弹窗里有操作按钮按钮的可访问名称必须清晰。这还只是一个组件。如果换成一个支持多选、搜索、分组的复杂下拉框需要记忆的上下文量和交互分支会立刻翻倍。问题的本质是无障碍规则是通用的而需要应用规则的组件是具体的。通用规则和具体组件之间有一条天然的鸿沟。没有一条规则会主动告诉你“你现在写的这个 div实际上是一个弹窗你需要做五件事”。传统团队是怎么填这条鸿沟的靠人肉记忆。一个人如果刚写过两个弹窗他大概率做对了如果团队同时在做表单、导航、数据表格、内容编辑器没有任何人能在脑子里同时维护所有组件的最新无障碍状态。于是出现了一个奇怪的现象团队不是不会做无障碍而是每次都像一个失去助记符的演员在台上临时回忆台词。A11y.md 的思路正是把这个问题从“记忆力问题”变成“工程问题”。2. A11y.md 是什么一个以 Markdown 为载体的无障碍上下文系统从项目名称和定位来看A11y 是无障碍 Accessibility 的缩写形式A11y.md 则暗示它把无障碍信息组织成 Markdown 文档。它要做的不是发明新的无障碍验证算法而是建立一套项目上下文让无障碍知识以结构化、版本化的方式持续存在。这里要重点解释两个词。第一个是“无障碍”。它不只是为了通过合规审计它意味着软件对使用键盘、读屏软件、低视力辅助技术的用户可用。这项工作有很强的场景特性同一个组件放在不同业务里无障碍要求会有差异同一个页面升级后旧的无障碍验收结论可能立即失效。第二个是“上下文系统”。在 AI 编程工具普及之前上下文主要指代码周围的逻辑结构。而今天上下文还有一个更实际的含义当前项目对本次编码行为的约束条件。例如这个项目当前的对比度基线是多少哪些组件属于已知的无障碍高风险区项目里已经做过哪些无障碍验证新增一个表单时需要遵循什么验收流程。A11y.md 就是把这类信息从团队负责人脑子里、从散落的文档里集中到一个固定的 Markdown 文件中让所有消耗上下文的角色都能读取它开发者看编码助手读CI 脚本解析MR 评审者核对。它和传统方案的区别可以用一个表格说清楚维度传统无障碍规范A11y.md 式上下文载体单独的网页文档经常与代码脱节随项目仓库一起版本化管理的 Markdown更新频率往往一年审一次随组件变更、缺陷修复持续演进读取方式靠人主动去查靠工具和工作流主动读取内容粒度通用规则集合项目基线 风险组件 验收清单失败代价文档更新了代码没改上下文与代码任何一侧变更都会留下记录需要说明的是从当前公开信息看A11y.md 更像一套理念驱动的工程模式而不是一个成熟的插件生态。它的价值不在于代码量多大而在于它把无障碍工程的问题边界画得非常清楚先解决上下文的组织和到达再谈自动修复。3. 为什么“上下文”是无障碍工程的分水岭过去十年的无障碍建设主流路径可以概括为写一份规范文档上线前做一次审计然后集中修一轮 bug。这听起来合理实际执行却有一个致命问题审计是低频的开发是高频的。一次审计可能只能在迭代结束时发现问题而开发期间大量看起来“很小”的无障碍选择已经把问题固定进了系统。等审计报告出来时修复成本已经不仅是改一个按钮而是要重构一个组件的交互逻辑。我把它比作交通管理无障碍规则是一本交通法规大多数开发者都承认要遵守但真正开车时会发现法规不会在你即将经过学校路段时主动提醒你减速。你需要的不是法规全集而是导航里的那句“前方学校路段注意减速”。上下文系统本质上就是这声导航提醒。它把无障碍知识从“百科全书模式”切换到“导航模式”。查询式的文档解决的是“想查的时候查得到”上下文解决的是“正在做这件事时推送到面前”。这是两种完全不同的信息到达效率。另一个不能忽视的变量是 AI 编码助手的普及。今天的编码助手已经具备读取项目内 Markdown 文件、理解约束条件的能力。这意味着一份写得好的 A11y.md可以让 AI 在生成代码时遵守项目的无障碍基线而不是只参考互联网上的通用代码模式。这大大降低了无障碍执行的成本。所以我的判断是无障碍工程的分水岭正在从“有没有规范”转向“有没有可被项目读取的上下文”。4. 适用场景与环境准备不是所有项目都需要立刻引入一套 A11y.md。先判断是否适合再动手。4.1 适合引入 A11y.md 的团队中后台产品团队。表格、表单、弹窗、树组件、日期选择器密集这些组件正是无障碍高风险区。把无障碍纳入交付标准的团队。例如产品面向公共服务、教育、医疗等有合规要求的领域。正在使用 AI 编码助手的团队。A11y.md 会直接成为助手的上下文来源性价比很高。已经厌倦“审一次修一批”循环的团队。想从源头减少无障碍缺陷而不是每次都在版本末期紧急处理。4.2 不建议一开始就套用的场景纯展示型、几乎无交互内容的一次性活动页。这类页面更需要基础对比度和图片替代文本不需要完整的上下文系统。希望“零人工、全自动修复”的团队。A11y.md 不是修复工具它是一套约束与提醒机制不会替你把烂组件变成好组件。项目本身就处于快速原型验证阶段页面结构和交互样式每天一变。此时维护上下文成本大于收益。4.3 落地所需环境以下依赖以通用实践为准具体版本以项目实际情况为准本文重点演示思路一个 Git 仓库用于存放 A11y.md 并跟踪变更Node.js 环境用于运行 CI 中的解析与校验脚本前端自动化测试工具如 Playwright、Cypress以及无障碍扫描库 axe-core支持项目上下文的编码助手可选但强烈推荐GitLab 或 GitHub用于配置 MR 模板和 CI 流水线。注意A11y.md 的价值建立在“项目有基本协作流程”之上。如果团队连代码评审和持续集成都还没跑通建议先补齐基础工程设施再引入无障碍上下文。5. 核心流程设计一份项目级 A11y.md写一份 A11y.md 之前先想清楚它要回答四个问题这个项目对无障碍的要求是什么哪些组件最需要被提醒目前有哪些已知问题怎么验证做完的东西符合要求围绕这四个问题设计流程可以拆成四步。5.1 第一步盘点项目的无障碍风险区不要从 WCAG 的全部条款出发而是从自己的代码出发。打开项目找出交互最复杂、复用率最高、历史缺陷最多的组件。中后台项目里几乎总是这几类弹窗和抽屉表格尤其是带排序、分页、行操作的表格复杂表单自定义下拉框和日期选择器导航菜单和面包屑图表和可视化组件。这一步的输出是一份“高风险组件清单”。它决定了 A11y.md 后续内容应该围绕什么展开。5.2 第二步定义项目的无障碍基线基线不需要贪多三到五条就够。它需要回答“我们至少要达到什么水平”。建议包含目标合规标准例如 WCAG 2.1 AA必须支持的读屏组合例如 NVDA Chrome、VoiceOver Safari键盘兼容要求例如所有可交互元素必须可以 Tab 聚焦焦点顺序与视觉顺序一致配色对比度要求例如正文文本至少达到 4.5:1动效和闪烁限制例如不要使用每秒闪烁超过三次的动效。基线不是越多越好。写得太多等于没有。基线的意义是让所有人在面临取舍时有统一判断标准。5.3 第三步设计上下文的分层结构一份可维护的 A11y.md内容应该分层而不是把所有内容揉在一起。建议分为四层项目基线跨组件、跨页面都成立的通用约束。高风险组件清单具体到组件级别的风险点和验收指引。已知缺陷正在排期或已修复的无障碍问题避免重复踩坑。验收清单代码提交前或 MR 评审时需要逐项确认的内容。这样分层的目的是让不同角色只读自己关心的部分开发者在写代码前看基线编码助手在生成代码时优先看风险清单测试人员在验收时对照清单逐项执行。5.4 第四步把上下文接入工作流A11y.md 写出来之后如果不进入工作流它和任何一份吃灰的文档没有区别。接入工作流至少要做三件事让编码助手在生成代码时引用这份上下文在 MR 模板中增加无障碍自检项在 CI 中增加对上下文的解析和校验。这一步最容易犯的错是把 A11y.md 当作文档项目来维护而没有把它变成代码变更流程的一个输入。上下文系统只有在被消费时才有价值。6. 完整示例从空项目到可执行的无障碍上下文下面用一个最小示例演示 A11y.md 的编写、接入和使用。示例采用通用技术栈思路重点是让整个闭环跑通。6.1 示例 A11y.md 文件在项目根目录创建docs/A11y.md# 项目无障碍上下文 本文档是项目无障碍开发的单一事实来源。 所有涉及页面交互、表单、弹窗、导航的代码变更必须先阅读本文档。 ## 无障碍基线 - 目标标准WCAG 2.1 AA - 读屏支持NVDA Chrome主要VoiceOver Safari次要 - 键盘要求所有可交互元素必须可聚焦焦点顺序与视觉阅读顺序一致 - 对比度要求正文文本对比度不低于 4.5:1大号文本不低于 3:1 - 动效要求不添加每秒闪烁超过 3 次的动效 ## 高风险组件 | 组件 | 风险点 | 验收指引 | | --- | --- | --- | | 全局弹窗 | 焦点未在弹窗内部循环关闭后焦点未返回触发元素 | 打开弹窗后按 Tab焦点只能循环在弹窗内部关闭后焦点返回触发按钮 | | 表单校验 | 错误提示未关联到输入框 | 输入框必须通过 aria-describedby 关联错误提示 | | 导航菜单 | 展开状态未暴露给读屏 | 展开按钮必须有 aria-expanded并管理对应区域状态 | ## 已知缺陷 - 修复中历史筛选面板的颜色对比度不满足 AA 标准已排期下个迭代 - 已验证登录表单的错误提示已关联 aria-describedby ## 验收清单 - [ ] 新增弹窗检查焦点移入、循环和返回 - [ ] 新增表单检查错误提示与输入框的关联 - [ ] 新增颜色检查对比度是否满足 AA - [ ] 新增自定义组件检查读屏软体可访问名称是否正确这份文档的核心价值在于“具体”。对比度基线和弹窗焦点管理不是从网上抄来的通用准则而是当前项目实际需要执行的要求。后续任何代码变更都能以它为坐标。6.2 让开发助手读取上下文当前主流编码助手大多支持通过项目内文档构建上下文。最保守的做法是在向助手描述任务时显式指定文档路径。请先阅读项目 docs/A11y.md然后基于其中的“无障碍基线”和“高风险组件”清单 审查下面这段弹窗组件的代码 粘贴组件代码 检查重点 1. 弹窗打开后焦点是否移入弹窗内部 2. 弹窗关闭后焦点是否返回触发按钮 3. 弹窗标题是否通过 aria-labelledby 关联 4. 是否对背景内容做了可访问性隔离。如果团队使用的是支持“项目规则文件”的编码助手还可以把 A11y.md 配置为助手默认读取的项目上下文。这样每次生成新代码时助手都不会忽略项目已有的无障碍要求。这里真正需要注意的坑是不要只为 AI 提供一段孤立的“无障碍请遵守 WCAG”指令。通用指令没有项目信息作为约束时生成的代码仍然会反复犯同样的错误。A11y.md 的价值就体现在这里——它把“遵守规则”细化成了“遵守本项目的规则”。6.3 将上下文接入 CI 验证A11y.md 除了给人看还能给脚本读。下面这个 Node.js 脚本演示一个常见思路解析 A11y.md 中的高风险组件清单检查项目测试目录中是否存在对应的无障碍测试文件。// scripts/check-a11y-context.js // 作用确保 A11y.md 中声明的高风险组件在测试目录中都有对应测试文件。 // 注意这是思路示例实际项目路径和命名规则需要按项目自身约定调整。 const fs require(fs); const path require(path); const a11yDoc fs.readFileSync(path.join(process.cwd(), docs/A11y.md), utf8); // 提取表格中第二列包含“组件”的行也就是高风险组件清单 // 示例行| 全局弹窗 | 焦点未在弹窗内部循环关闭后焦点未返回触发元素 | ... | const componentRows a11yDoc .split(\n) .filter(line line.startsWith(|) line.includes(组件) !line.includes(组件 |)) .map(line line.split(|)[1].trim()); if (componentRows.length 0) { console.log([A11y.md] 未发现高风险组件清单跳过检查。); process.exit(0); } const testsDirectory path.join(process.cwd(), tests); const missing []; function walk(dir) { let results []; const list fs.readdirSync(dir, { withFileTypes: true }); for (const item of list) { const fullPath path.join(dir, item.name); if (item.isDirectory()) { results results.concat(walk(fullPath)); } else { results.push(fullPath); } } return results; } const allFiles fs.existsSync(testsDirectory) ? walk(testsDirectory) : []; componentRows.forEach(componentName { const hasTest allFiles.some(file { const base path.basename(file); return ( file.includes(componentName) /a11y|accessibility|axe/.test(base) ); }); if (!hasTest) { missing.push(componentName); } }); if (missing.length 0) { console.error([A11y.md] 以下高风险组件缺少对应的无障碍测试); missing.forEach(name console.error( - ${name})); console.error(请在 MR 中补充 axe 或人工验收脚本或在 A11y.md 中说明豁免理由。); process.exit(1); } console.log([A11y.md] 所有高风险组件均有测试覆盖。);这个脚本的核心逻辑并不复杂但它体现了一个关键转变**A11y.md 中的风险清单不再是文档承诺而是 CI 的一个输入条件。**当团队新增了一个高风险组件但忘记做无障碍验证时流水线会直接给出提示。在package.json中注册脚本{ scripts: { check:a11y-context: node scripts/check-a11y-context.js } }然后在 CI 的测试阶段加入npm run check:a11y-context这样每次提交代码A11y.md 与测试之间的断层就会被自动发现。7. 运行验证与效果评估接入 A11y.md 之后不要只看“文档写好了”就认为任务完成。需要从三个维度验证效果。7.1 验证文档本身是否可用请一个没参与编写 A11y.md 的开发同事让他根据文档完成一个真实的组件修复任务。观察他的行为他是否能快速找到对应的风险点和验收指引他是否知道改动完成后要做什么验证他是否能判断当前项目对键盘和读屏支持的目标是什么如果同事完全依赖文档就能完成修复说明 A11y.md 的内容密度合适。如果他一再追问细节说明文档缺少必要的上下文如果他完全不看文档说明文档没有进入工作流需要检查接入方式。7.2 验证自动化检查是否有效在 CI 中执行npm run check:a11y-context预期结果有两种通过说明 A11y.md 中声明的所有高风险组件都有对应的测试覆盖失败CI 会列出缺少测试的组件名称。此时处理方式不是“删掉 A11y.md 中的组件来让检查通过”而是补充测试或者给出明确的豁免理由。如果第一次运行就报了十几个缺失组件不要慌这正是建立基线的好机会——把已有组件逐步补齐后续新增组件就会天然遵守规则。7.3 验证开发流程是否真正受益建议团队自己关注三个数据不需要很精确但要有变化趋势同一功能的无障碍缺陷在“开发阶段被发现”的比例版本末期因无障碍问题返工的次数新同事从接手代码到理解项目无障碍规范所需的时间。这些数据不一定要做成报表只要团队能感受到变化就可以了。如果引入了 A11y.md 但上述指标没有任何改善说明问题不在文档内容而在上下文没有被正确地消费和强制。8. 常见问题与排查方法问题现象可能原因排查方式解决方案A11y.md 写得很全但没人看文档没有进入任何工作流检查编码助手配置、MR 模板、CI 脚本是否引用文档把 A11y.md 设置为编码助手项目上下文在 MR 模板中增加“是否涉及高风险组件”自检项不知道文档里该写什么试图覆盖 WCAG 全部条款检查内容是否过于通用、缺少项目特定信息只写当前项目的高风险组件清单、基线和已知缺陷控制在一页以内文档维护成本过高把过程记录、代码细节、任务记录都写进文档观察文档更新频率是否集中在代码评审阶段明确边界A11y.md 只记录“要求”和“结论”不记录实现过程自动化检查总是报错高风险组件名称与测试文件名不匹配查看 CI 日志中列出的缺失组件名统一组件命名与测试文件命名规则或者在脚本中维护组件到文件的映射编码助手生成代码仍不符合无障碍要求只配置了通用指令没有提供项目上下文检查每次生成代码时助手是否读取到 docs/A11y.md在 prompt 中显式引用 A11y.md并使用文档中的风险清单做自查团队没有自动化测试基础设施A11y.md 超前于工程能力评估目前是否有 Git 和 CI先把 A11y.md 用于人工评审同时逐步搭建测试框架9. 最佳实践与工程建议最后分享几条从实践中沉淀下来的建议适用于大多数准备引入 A11y.md 的团队。9.1 控制文档长度一页能看完才算合格A11y.md 不是无障碍教科书。如果它需要读者滚动三屏才能看完说明内容过载。最佳状态是开发者打开之后30 秒内能定位到与自己改动相关的部分。建议基线不超过 5 条高风险组件不超过 20 个验收清单保持在二三十项以内。超出的部分用附件链接承载不要让 A11y.md 变成“巨型百科”。9.2 把它当作测试输入而不是文档资产A11y.md 最大的对手不是没人看而是“看了也不用”。让它成为测试输入是防止它沦为废纸的最有效方式。最简单的方式就是把 A11y.md 的风险清单和 CI 脚本绑定。代码里新增了一个组件但它属于 A11y.md 声明的高风险类型却没有对应测试流水线就亮红灯。此时文档不再只是提醒而是约束。9.3 写在代码变更发生的时刻上下文只有在正确的时间出现才有价值。不要在项目上线前才补写 A11y.md而在新组件设计、新页面开发、代码评审这三个节点主动使用它新组件设计时用风险清单判断是否属于高风险组件新页面开发时用验收清单逐项检查代码评审时用基线检查 MR 中的新增颜色、焦点处理和可访问名称。9.4 定期回访保持上下文的时效性每个迭代或每个月安排一次 A11y.md 维护任务。更新已知缺陷状态移除已经固化到组件库中的通用规则新增逆向案例。A11y.md 最怕的不是内容少而是内容过时。一份记录了“已知缺陷”却从不更新的文档很快就会让团队丧失对它的信任。9.5 与组件库建设结合如果是多项目团队可以考虑把公共组件库中已经验证过的无障碍实现沉淀成模板代码让 A11y.md 只负责“当前项目特殊的地方”。这样可以进一步减少重复劳动。写到这里我已经把 A11y.md 的核心思路、场景适配和落地路径讲清楚了。无障碍开发不缺规则缺的是让规则在正确时间出现的上下文。与其等审计报告出来再补不如从下一个迭代开始为项目写一份简短、具体、可以被工具读取的 A11y.md。