main 元素必须全局唯一且稳定存在,所有路由更新应在内部进行内容替换,禁止条件渲染或销毁重建;nav 和 header 需按语义分层并明确作用域;动态内容更新须通过 aria-live 主动广播;section/article/aside 应严格遵循语义规范,标题层级必须连续完整。

main 必须唯一且稳定,不能随路由销毁重建
SPA 路由切换时,main 被反复移除或条件渲染(比如 v-if 或 ngIf),会导致屏幕阅读器丢失“主内容起点”。用户按 M 键跳转时找不到目标,或听到空白/错误上下文。
正确做法是:整个 DOM 中只存在一个 main 元素,所有视图更新都在其内部用 innerHTML 替换内容,或用框架的 slot / children 渲染机制注入新内容。加载态、空态、错误态都应保留在 main 内部,仅通过 CSS 控制显隐(如 display: none)。
- 全局弹窗、Toast、遮罩层必须放在
body直接子级,配role="alert"或aria-live="polite" - React/Vue 中避免把
main包在组件条件逻辑里;改用className切换状态类 - 检查工具:运行
document.querySelectorAll('main').length,结果必须恒为 1
nav 和 header 必须按作用域分层,不能混用
顶部主导航、文章目录、面包屑、侧边菜单功能不同,但常被塞进同一个 nav 或 header,导致屏幕阅读器无法区分“全局”和“局部”,用户导航时会迷失。
每个导航需独立声明并明确作用域:
- 顶部 Logo + 主菜单 →
<header role="banner"><nav aria-label="主导航">...</nav></header> - 文章页右侧 TOC → 放在
main内部,用<nav aria-label="本文目录"></nav> - 面包屑 → 单独
<nav aria-label="面包屑"><ol>...</ol></nav>,当前项加aria-current="page"
错误示例:<nav><h2>导航</h2>
<ul>
<li>首页</li>
<li>目录</li>
</ul></nav> —— 混淆了层级与语义,读屏会误判为同一组链接。
动态内容更新必须触发可访问性广播,不能静默替换
SPA 的 DOM 局部刷新对屏幕阅读器是“无声操作”。用户看不到 loading 动画,也听不到“列表已加载完成”,除非你主动通知。
关键动作必须配合 ARIA 实时区域:
- 异步加载成功后,在
main外部(但仍是body子级)放一个<div aria-live="polite" class="sr-only">共找到 12 条匹配结果</div> - 表单提交后,用
aria-live播报 “保存成功”,而非仅靠颜色变化或 Toast - 避免纯 CSS 动画反馈(如淡入),屏幕阅读器无法感知;所有状态变更需同步设置
aria-busy、aria-expanded等属性
注意:aria-live 区域需有 tabindex="-1" 才能被焦点捕获,否则部分读屏可能忽略。
section/article/aside 边界模糊会让键盘导航断裂
当 section 嵌套过深、article 被当成卡片容器、aside 里塞按钮时,Tab 键顺序会跳跃,屏幕阅读器会把辅助内容当主内容朗读。
判断是否滥用的标准很直接:
-
section必须自带h2–h6标题,且该标题能准确概括区块内容 -
article应可独立分发(RSS、邮件推送)、有明确作者/发布时间,不是“商品卡片”或“用户头像块” -
aside只放与当前main内容相关但非核心的信息(如术语解释、参考资料),不放操作按钮或表单
金融报表中常见错误:<section><p>欢迎关注我们</p></section> —— 无标题、无可分发性、无结构意义,纯属语义污染。
最易被忽略的其实是标题层级断裂:页面从 h2 开始、main 外堆了三个 h1、小节用了 h3 但主内容还没出现 h2。这种断裂会让屏幕阅读器生成错乱大纲,用户根本无法靠 heading 导航定位年度汇总或对比图表。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











