HTML无障碍落地难的根源在于lang、<main>唯一性、标题层级连续性未设为构建硬门槛:lang须符合IETF BCP 47(如zh-CN)并校验;<main>必须是body直接子元素且全局唯一;标题需连续不跳级,三者须通过CI、组件库规则和编辑器实时拦截强制保障。

团队里 HTML 无障碍标准无法落地,根本原因不是没人知道 alt 要写,而是没把 lang、<main> 唯一性、标题层级连续性这三项变成构建流程里“不满足就卡住”的硬门槛。
lang 属性必须匹配 IETF 标准且带地区码
很多项目在 SSR 或 i18n 切换时用 lang="{{ locale }}" 拼接,结果产出 lang="zh" 或 lang="ch"——这类值不符合 IETF BCP 47 规范,NVDA 和 VoiceOver 会 fallback 到英文朗读,中文内容被逐字按英语音节切分,用户完全听不懂。
- 强制要求格式为
zh-CN、zh-TW、en-US等,后缀不可省略 -
<html>标签上声明,不能靠<meta>或局部lang补救 - CI 阶段用正则
^[a-z]{2}(-[A-Z][a-z]{1,})?$校验,非法值直接 fail build - React/Vue 中避免运行时 JS 拼接,确保首屏 HTML 的
<html lang="">已渲染正确
<main> 必须是 <body> 的直接子元素且全局唯一
常见错误是把 <main> 包在 <div class="container"> 里,或服务端模板多次 include 导致多个 <main>。这时屏幕阅读器要么跳过整个主内容区,要么报 critical 结构错误,用户按 M 键进不了正文。
- 用 Cheerio 或 jsdom 在 CI 阶段解析产物 HTML,检查
document.body.children中main元素数量及是否为 direct child - 禁止在任何原子/分子组件(如 Card、Page)中输出
<main>,只能出现在最顶层 Layout 或 SSR 模板 - 在组件库 ESLint 规则中加自定义规则,匹配 JSX/Template 中的
<main开标签并报error - Layout 组件加运行时断言:
if (document.querySelector('main')?.parentElement !== document.body) throw new Error('main must be direct child of body')
标题层级必须连续,不能跳级也不能隐藏后残留
用户按 H 键导航时,<h2> 后直接 <h4> 会让屏幕阅读器误判章节结束。这不是体验差,是信息架构逻辑塌方——用户以为内容结束了,其实后面还有三段。
立即学习“前端免费学习笔记(深入)”;
- 遍历所有
h1–h6,记录当前期望层级,遇到跳级(如 h2→h4)即报错 - CSS 的
display: none或visibility: hidden不等于移除语义,必须删 DOM 节点,或改用aria-hidden="true"+visually-hidden类替代 - 第三方组件(如图表库、富文本)默认输出
<h2>时,需封装 wrapper 组件动态重写其标题层级 - 每个
<section>应以<h2>或更高级别标题开头;HTML5 允许重置层级,但前提是显式提供
真正难的不是写对一个 <nav>,而是确保它在 20 个组件、7 个微前端子应用、3 套 CMS 模板里始终承载相同语义——这没法靠人盯,只能靠工具卡口:CI 拦截、组件库约束、VS Code 实时标红,三者缺一不可。漏掉任意一环,a11y 就退化成 Wiki 上的装饰性文字。



















