lang属性必须写在标签上且值为zh-cn、zh-tw或zh-hk,否则屏幕阅读器会读错语言;须为直接子元素且唯一;标题需连续递进;按钮须用语义化而非div模拟。

lang 属性写错或缺失,屏幕阅读器立刻“失语”——不是读成日语腔,就是断句混乱听不懂。必须用 lang="zh-CN"、lang="zh-TW" 或 lang="zh-HK",且只能直接写在 标签上。
lang 属性必须直接写在 html> 标签上,不能靠 JS 或 meta 补救
动态渲染(React/Vue)时,很多人习惯在组件里用 useEffect 或 mounted 去 setAttribute 修改 lang,但 NVDA、VoiceOver 等工具只读首屏 HTML 的 。JS 后置设置等于没设。
- SSR 或静态生成阶段就必须输出正确的
-
<meta http-equiv="content-language" content="zh-CN">完全无效,axe 工具仍报“严重”错误 - 局部语言切换(如引文)可用嵌套
lang:<p lang="en">Hello</p>,但顶层必须先对
必须是 的直接子元素,且全页唯一
包在 <div class="wrapper"> 或 <code><section></section> 里,<main></main> 就失效了。NVDA 会跳过它,或当成普通区块——主内容直接“消失”。
- 校验工具(如 axe、WAVE)会标红:
Multiple <main> elements</main>或<main> not a direct child of </main> - 常见误用:把
<header></header>和<main></main>都塞进同一个<div> 容器 <li> <code><main></main>内部再嵌套<main></main>是硬性禁止,HTML5 规范不允许多重主区域 - CSS 隐藏
display: none的标题不会从语义流中移除,仍参与层级计算 - 组件库卡片默认用
<h2></h2>,若嵌在<main></main>的<h2></h2>下,就变成 H2→H2,破坏递进 - 动态插入标题时,必须校验当前上下文最大层级,再决定用
<h3></h3>还是<h4></h4> - 原生
<button type="button"></button>自带焦点、空格/回车触发、禁用态样式,无需额外 ARIA -
<nav></nav>必须有aria-label,否则多个<nav></nav>共存时,未标注的那个对辅助技术“不可见” -
<nav aria-label="主导航"></nav>和<nav aria-label="页内目录"></nav>是最低要求,不能省略
标题层级断裂会让屏幕阅读器“掉层”,不是提示不准,是结构塌方
用户按 H 键跳转时,依赖的是连续递进的 <h1></h1>→<h2></h2>→<h3></h3>。一旦 <h2></h2> 后跟 <h4></h4>,中间缺 <h3></h3>,他们就会误判章节结束。
按钮和导航区不用 div 模拟,语义标签自带焦点与角色
用 <div onclick="submit()"> 写按钮,键盘用户 Tab 不到,屏幕阅读器也读不出它是“按钮”。加 <code>role="button" 不等于有了行为——它不会响应空格键,必须手动监听 keydown。
最常被忽略的不是怎么加 ARIA,而是语义结构本身是否成立——<main></main> 被包住、lang 被 JS 拖后、<h3></h3> 被 CSS 隐藏却还在 DOM 里,这些都不是“锦上添花”的优化项,而是让屏幕阅读器连门都找不到的基础门槛。











