必须优先将和替换为,改为,改为,改为,表单控件须配显式且严格匹配id。

整站可访问性(a11y)达标不是靠一次“大扫除”完成的,尤其在敏捷工程中,强行要求所有页面同步改造会卡死迭代节奏。真正可行的路径是:用 HTML 本身的能力做渐进式修复,把 aria- 属性、语义化标签、焦点管理等控制在单个组件或页面范围内落地,每次交付都比上一版更可访问。
哪些 HTML 元素必须优先替换
很多团队花时间写 ARIA 脚本,却忽略最基础的语义化缺失——这反而让屏幕阅读器更困惑。修复顺序应倒过来:先改结构,再补属性。
- 所有
<div onclick="..."> 或 <code><span class="button"></span>都要替换成<button></button>,否则无法键盘聚焦、缺少原生 role 和语义 <div role="navigation"> 改为 <code><nav></nav>;<div role="main"> 改为 <code><main></main>;<div role="banner"> 改为 <code><header></header>- 表单控件必须有显式
<label for="id"></label>,且for值严格匹配id;避免仅靠视觉位置隐式关联 - 如果元素已有原生语义(如
<button></button>、<input type="checkbox">),不要加role="button"或aria-checked—— 浏览器和读屏器会自行处理 -
aria-live只用于动态更新区域(如搜索建议、表单错误提示),且必须配合aria-atomic和aria-relevant控制播报粒度,否则会刷屏 -
aria-hidden="true"不能加在包含焦点元素的父容器上(比如整个弹窗div),否则键盘用户进不去——应只隐藏纯装饰性内容 - 页面级路由变更后,应立即将焦点移到新内容起始处,推荐用
document.getElementById("main-content").focus(),前提是该元素有tabindex="-1" - 模态框打开时,用
inert属性(或 polyfill)禁用背景内容,而非仅靠aria-hidden;同时用focus-trap库限制 Tab 键循环在模态框内 - 避免在组件挂载时自动
focus()输入框——除非是搜索页等明确以输入为核心的场景,否则会打断用户当前操作 - 用
axe-core的axe.run(element)对单个组件 DOM 片段运行检测,而不是整页——这样能精准定位 PR 中引入的问题 - CI 中配置
jest-axe+@testing-library/react,在单元测试里断言关键节点是否通过mustHaveRole、mustHaveLabel等规则 - 禁止将
aria-*属性硬编码在 JSX 里却不校验其值有效性(例如aria-labelledby="missing-id")——这类错误会被 axe 捕获为aria-valid-attr-value
ARIA 属性什么时候该加、什么时候不该加
ARIA 是补丁,不是替代品。滥用 role 或 aria-* 属性反而破坏可访问性,尤其当它覆盖了原生语义时。
焦点管理在 SPA 中如何不打断导航流
单页应用里路由切换后,焦点常留在旧位置或丢失,导致屏幕阅读器用户“掉线”。这不是 JS 框架的问题,而是没按标准接管焦点。
CI/CD 中怎么验证 HTML 可访问性增量
靠人工测 a11y 不可持续,但全量自动化扫描又容易误报。关键是把检查嵌入到最小可交付单元中,只验“本次改了什么”。
最常被忽略的不是技术点,而是“谁来判断修复是否生效”:别只依赖 axe 报告里的“pass”,要实际用 VoiceOver/NVDA+键盘走一遍核心流程。一个 tabindex 顺序错位,可能让整个表单不可用,而工具根本不会报错。











