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

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



















