可访问性代码退化本质是语义链被无声切断,而非单纯漏加ARIA属性;lang和<title>必须在首次渲染时正确设置,因读屏器只读初始HTML值,后续JS修改不触发重播报,空lang导致语音错配,写死title使AT用户无法区分页面上下文。

可访问性代码退化不是“漏加 aria-label”,而是语义链在日常修改中被无声切断——它不报错,但会让读屏器跳过导航、键盘用户卡死在表单、SEO权重持续流失。
为什么 lang 和 <title> 必须在首次渲染时就正确
很多团队以为“JS 后续改 document.title 就行”,但读屏器(如 NVDA、VoiceOver)只读取初始 HTML 的 <title> 和 <html lang="">;后续 JS 修改不会触发重新播报。空 lang 会导致语音引擎错配(中文站读英文),<title> 写死为 “My App” 则让所有页面共享同一标题,AT 用户无法区分当前页上下文。
-
lang必须写在<html>开头标签里,且值要精确:大陆用zh-CN,香港用zh-HK,不能留空或写成zh - 纯静态模板必须服务端动态渲染
<title>和<h1>;SPA 场景下,React 用useEffect同步 title,Vue 用onMounted+document.querySelector('h1').textContent - CI 中可用 Puppeteer 启动无 JS 模式访问首页,断言
document.documentElement.lang是否非空、document.title是否包含当前页面关键词
<nav> 缺 aria-label 是无障碍失效的高发点
第三方模板常只放一个裸 <nav><ul><li>Home</li></ul></nav>,读屏器会报“navigation”,但用户根本不知道这是主导航、面包屑还是侧边栏。更糟的是,多个 <nav> 共存时,没 label 就等于全不可识别。
- 每个
<nav>都必须带具体aria-label:aria-label="主导航"、aria-label="面包屑导航"、aria-label="页脚快捷链接" - 面包屑中当前页链接必须设
aria-current="page",否则 AT 用户无法定位当前位置 - 禁止用
aria-label="Navigation"这类泛化值——它和没标一样;也别依赖 CSS 隐藏文本替代,读屏器可能跳过 - CI 可用 axe-core 扫描:
axe(document, { runOnly: ['landmark-no-duplicate'] })检测重复 landmark,['navigation-has-label']检查 nav label 缺失
表单控件的语义断裂比样式错位更致命
“看起来能用”不等于“可访问”。一个没 for 关联的 <label>,键盘聚焦时读屏器完全不报字段用途;disabled 只靠 CSS 实现,键盘仍可 tab 进去、读屏器仍报“可编辑”;required 仅靠星号或 aria-required="true",则校验逻辑与 AT 行为脱钩。
立即学习“前端免费学习笔记(深入)”;
-
<label>和<input>必须严格配对:<label for="email">邮箱</label>+<input id="email" name="email">;ID 值大小写、连字符必须完全一致 - 禁用状态必须用原生
disabled属性,不能只加 class;同理,必填项必须用required,而非仅靠aria-required - 自定义组件(如 tabs、switch)优先用原生语义标签:
<button role="tab">而非<div role="tab">;role只是补位,不能替代原生语义 - CI 中跑 Lighthouse 的 Accessibility audit,重点关注 “Form elements have accessible names” 和 “Interactive elements have sufficient contrast”
HTMLHint 怎么配才真防退化
默认 HTMLHint 规则太宽泛,容易误报或漏报。真正起作用的,是聚焦可访问性退化高发点的精简规则集。
- 强制
doctype-first:防止 Quirks Mode 导致 AT 渲染异常 - 启用
attr-lowercase和id-class-value:避免因大小写或空格导致 JS 查询失败(document.getElementById("User-Name")≠user-name) - 加
img-alt-require,但排除装饰图:alt=""允许,alt="图片"或空格alt=" "必报错 - VS Code 安装 HTMLHint 插件后,在
.htmlhintrc里只开这 4 条,其他全关——规则越多,开发者越习惯 ignore
退化最常发生在“改一行样式”的瞬间:删掉 aria-label 图省事、把 <button> 换成 <div onclick>、在模板 include 里注释掉 lang 属性……这些动作本身不报错,但每一步都在削弱语义链。防退化的关键,不是写得完美,而是让每次提交都过得了机器校验——哪怕只守住 lang、aria-label、label-for 这三条底线。



















