必须用<html lang="zh-CN">开头且<main>唯一并为<body>直接子元素,标题层级不可断裂;缺失lang、嵌套<main>或跳级标题将导致屏幕阅读器导航失效、乱码或跳过正文。

必须用 <html lang="zh-CN"> 开头,否则屏幕阅读器大概率读成英文或乱码;<main> 只能出现一次且直接子元素不能是 <div>;标题层级断裂(比如 <h2> 后直接 <h4>)会让导航完全失效。
lang 属性写错或缺失的后果
不写 lang,或写成 lang="ch"、lang="cn"、lang="zh",浏览器和屏幕阅读器(如 NVDA、VoiceOver)就无法准确识别语言。中文内容可能被当作日文、韩文甚至英文朗读,语调错乱、分词错误,用户根本听不懂。
正确写法只有两种主流值:lang="zh-CN"(简体中文)、lang="zh-TW"(繁体中文)。港澳地区可用 lang="zh-HK"。不要省略后缀,zh 单独存在不是 IETF 标准值,AT(辅助技术)兼容性差。
-
<html>标签上必须声明,不能只在<meta>或某段文字里补 - 多语言页面中,局部语言切换要用
lang属性嵌套,比如一段英文引文:<p lang="en">...</p> - React/Vue 中动态渲染时,别用 JS 拼接
lang值——得确保 SSR 或初始 HTML 就带对
<main> 和 <nav> 的使用边界
<main> 是页面唯一主内容区,它必须直接位于 <body> 下,不能包在 <div>、<section> 或其他容器里。如果嵌套了,NVDA 等工具会跳过它,或者误判为普通区块。
立即学习“前端免费学习笔记(深入)”;
<nav> 不是“所有链接集合”,而是**有明确导航意图的链接组**:主导航、面包屑、页内锚点跳转列表可以,但文章末尾的“相关阅读”或登录/注册链接就不该放进去——AT 用户按 N 键快速跳转时,会被带进一堆无关链接,体验崩坏。
- 多个导航区要加
aria-label区分,比如:<nav aria-label="主导航">、<nav aria-label="页内目录"> - 面包屑用
<ol>而非<ul>,并给当前页加aria-current="page" -
<main>内部禁止再出现<main>,校验工具(如 axe)会直接报严重错误
标题层级断裂的真实影响
屏幕阅读器用户靠 H 键(如 H1/H2/H3)逐级跳转内容。一旦出现 <h2> 后跟 <h4>,中间缺了 <h3>,他们就会“掉层”——以为章节结束,实际内容还在继续。这不是提示问题,是逻辑结构塌方。
常见断裂场景:CSS 隐藏了某个 <h3> 却没同步移除 DOM;JS 动态插入标题但没检查层级;组件库默认用 <h2> 作为卡片标题,嵌套在 <h2> 主区域下,变成 H2→H2。
- 每个
<section>应以一个<h2>或更高级别标题开头(HTML5 允许<section>内部重置层级,但前提是显式提供标题) - 避免用 CSS 的
display: none隐藏标题——改用aria-hidden="true"+ 视觉隐藏类,或直接删 DOM - 用浏览器插件(如 HeadingsMap)实时检查标题流,比人工数标签靠谱得多
DOCTYPE 和 charset 的硬性前置条件
没有 <!DOCTYPE html>,浏览器会进入怪异模式(Quirks Mode),导致 <main>、<nav> 等语义标签被忽略,AT 完全无法识别结构。这不是警告,是功能级失效。
<meta charset="UTF-8"> 必须出现在 <head> 最前面(最好第一行),晚于它或放在 <title> 后,部分 WebView(尤其 Android 内嵌)会解析失败,中文变乱码,AT 读出来就是一串“”。
- 不要写
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">—— 过时且兼容性差 - 服务端返回的 HTTP Header 中
Content-Type也必须含charset=utf-8,否则前端 meta 可能被覆盖 - 生成静态页的工具(如 Hugo、Jekyll)要确认模板里
<meta charset>位置不可被插件挪动
最常被跳过的其实是 <main> 的直系子元素约束和 lang 的完整 IETF 格式——这两处出错,整个结构语义就从根上断了,后续加再多 ARIA 也救不回来。



















