可访问性规范落地失败主因是工程链路缺硬闸,未将检查嵌入保存、提交或构建环节;必须强制执行lang属性、标签等critical级规则,并通过vs code插件、git hooks和ci中断实现实时拦截。

为什么可访问性规范落地失败,90%是因为没卡住入口
团队里没人反对“支持屏幕阅读器”,但一到写代码就回到
实操建议:
-
htmlhint配置必须启用attr-req-alt、id-unique(防多个<main></main>)、headings(防标题跳跃)三条规则,其他可选 - VS Code 安装
HTMLHint插件,保存时实时标红,新人第一天就能看到<img>缺alt被拦住 - Git hooks 用
husky+lint-staged,提交前跑htmlhint src/**/*.html,错误不修复禁止 commit - CI 流程中加
axe-cli --disable-colors --save-logs=axe-report.json,失败直接中断构建,不是发邮件提醒
lang 属性和 标签是两条不可妥协的红线
这两项不是“建议”,而是可访问性校验工具报 critical 级别的硬性门槛。一旦出错,NVDA/VoiceOver 直接跳过主内容或读错语言,用户根本无法使用。
常见错误现象:
-
或—— 不符合 BCP 47 标准,屏幕阅读器 fallback 到英文发音 -
<main></main>包在<div class="container"> 里,或子组件模板里重复输出 <code><main></main> - 多语言页面只改了
document.body.innerHTML,但没同步更新document.documentElement.lang -
lang值必须为zh-CN/zh-TW/zh-HK,后缀不可省略;SSR 模板中必须动态注入,不能靠 JS 运行时补 -
<main></main>必须是的直接子元素,且全局唯一;React/Vue 中需由根布局组件统一控制,禁止子组件 render 出<main></main> -
<nav></nav>只能包裹导航链接,混进<form></form>或<button></button>就破坏语义;搜索框应单独放,不要塞进<nav></nav> -
<section></section>必须带标题(<h2></h2>或更高级),否则 axe 会报"landmark-no-label";纯视觉分隔不用<section></section> -
<aside></aside>不是“右边那块区域”,而是指与当前内容相关但非核心的补充信息(如作者简介、同类文章),广告位放这里反而是违规 - 用
<time datetime="2026-07-01"></time>替代纯文本日期,让日历类 App 和搜索引擎提取结构化时间 - 所有关键
data-属性旁必须跟注释,例如:<div data-user-id="123"> <!-- 后端返回,不可为空 --><li> <code>aria-label不能写模糊描述如"点击跳转",要写目标页实质,如aria-label="打开公司介绍页面" - 属性按顺序写:
class→id→data-→src/for/type/href→title/alt→role/aria-→tabindex→style;iOS WKWebView 和安卓 X5 内核对乱序解析容忍度极低 - 禁用
<!-- TODO -->,换成<!-- @todo 张三 2026-07-20: 替换为 useAccessibility hook -->
参数差异与兼容性影响:
语义化标签不是美化,而是结构契约
团队常把 <nav></nav>、<section></section> 当成“锦上添花”,结果小程序 WebView 和 PWA 中导航失效、SEO 内容丢失。这不是样式问题,是 DOM 结构没满足辅助技术识别前提。
使用场景与容易踩的坑:
注释和属性顺序是协作隐性成本的放大器
一个没写清楚的 data- 属性或错位的 aria-label,会让后续维护者花两小时猜意图。可访问性不是“加上就行”,而是“让别人能看懂你为什么这么加”。
实操要点:
真正难的不是写对第一个 <main></main>,而是确保第 37 个组件模板里依然没把它嵌套进 <header></header>;不是记住所有 ARIA 规则,而是让每次保存都自动提醒你漏了 alt。











