必须全局唯一,否则屏幕阅读器跳转失效;一个页面只能有一个main,嵌套在nav或header中会破坏可访问性逻辑,导致“跳转到主要内容”快捷键失效、辅助工具报错及搜索引擎降权。

<main></main> 必须全局唯一,否则屏幕阅读器跳转失效
很多开发者以为加了语义标签就“达标”了,实际不是。最常出问题的是 <main></main>:Next.js 或 Vue 的布局组件里套一个,页面组件里再 render 一个,DOM 中就会出现多个 <main></main>。屏幕阅读器按 M 键跳转时只认第一个,后续的全被忽略——用户得手动听完整页才能找到正文。
- 检查方法:DevTools → Elements 面板顶部看是否只有一个
<main></main>,且它只包裹用户真正要操作/阅读的部分(如商品列表、表单主体、文章正文) - 错误写法:
<main><header>...</header><nav>...</nav><div class="content">...</div></main>(把导航、banner 全包进去了) - 性能影响极小,但辅助技术会多做无效 DOM 遍历,语音响应变慢
section 不是 div 的语义升级版,没标题就别用
<section></section> 的核心判断标准就一条:这块内容是否自带 <h2></h2>–<h6></h6>?能不能被 RSS 抓取或邮件推送?如果只是用它包个卡片列表、轮播图容器、或者一堆无标题的 <p></p>,那它就是语义污染。
- 错误现象:屏幕阅读器生成大量无意义的“第 X 节”导航节点,用户反复跳过却找不到重点
- 正确替代:纯视觉分隔用
<div>;侧边栏用 <code><aside></aside>;页脚用<footer></footer>;独立文章用<article></article> - 注意:
<section></section>嵌套不能超过三层,否则认知障碍用户会迷失结构 - 工具会直接报错:
ARIA input field has both a visible label and an aria-label - 优先用
<label></label>:支持点击聚焦、语义清晰、无需 JS 维护 - 仅在无法包裹(如动态渲染的弹窗表单)或需要补充上下文时才用
aria-label,且文本需不同(例:<label>邮箱</label>+aria-label="用于接收验证邮件的邮箱地址") - 验证方式:Chrome DevTools → Elements 面板顶部显示 “Standards” 才合格;若显示 “Quirks”,立刻检查源码第一行
- 常见坑:VS Code 右下角编码显示为 “UTF-8 with BOM”;PHP 模板 include 前有空白;HTML 文件开头有注释或空行
- 修复动作:保存为纯 UTF-8(无 BOM),确保
DOCTYPE html>独占第一行,前面零字符
aria-label 和 <label></label> 别共存,否则触发重复播报
当一个 <input> 同时有 for/id 关联的 <label></label>,又设置了内容相同的 aria-label,辅助技术会计算两次可访问名称——这违反 WAI-ARIA “唯一可访问名称”规范。
DOCTYPE 前不能有任何字符,包括 BOM 和空格
IE 和旧版 Edge 一旦进入 Quirks Mode,所有 ARIA 属性和 HTML5 语义标签都会被静默忽略——屏幕阅读器读不到任何结构信息。
最常被忽略的其实是语言与焦点:lang 属性漏写,屏幕阅读器发音错乱;tabindex="-1" 加了却不配 focus(),键盘用户卡在模态框外;aria-live 区域更新了,但 DOM 没真实插入新节点——这些都不是“结构问题”,而是“执行断点”。











