应使用 替代所有手写 class="menu" 的 div,因其是唯一能明确标识导航的原生地标;多个 需用 aria-label 区分,禁止嵌套,且不可用 或 替代。

用 <nav></nav> 替代所有手写 class="menu" 的 div
后台系统里最常见的可访问性漏洞,就是把导航栏写成 <div class="header-nav"><ul>...</ul></div>。这种写法对屏幕阅读器来说只是“一个 division”,它根本不知道这是导航入口,也不会主动提示“按 Tab 可跳转到导航”。<nav></nav> 是唯一能明确告诉辅助技术“这里是一组导航链接”的原生地标。
实操时注意三点:
- 每个
<nav></nav>都该有明确的语义目的,比如主导航、侧边菜单、面包屑、页脚链接组 —— 不要只为了套标签而套 - 如果页面含多个
<nav></nav>(如顶部主导航 + 左侧权限菜单),必须加aria-label区分,例如<nav aria-label="主导航"></nav>和<nav aria-label="系统管理菜单"></nav> - 不要嵌套
<nav></nav>:一个<nav></nav>内部再包一个<nav></nav>会让可访问性树混乱,浏览器可能忽略内层角色
为什么 <header></header> 不能当导航容器用
很多后台模板把整个顶部区域(Logo + 用户头像 + 搜索框 + 导航)全塞进一个 <header></header>,然后给它加 role="navigation"。这是错的 —— <header></header> 的语义是“页眉”,不是“导航”。即使加了 ARIA 角色,原生语义优先级高于 ARIA,辅助技术仍会把它读作“页眉”,而非“导航”。
正确做法是:
-
<header></header>只包裹 Logo、主标题、用户信息等非导航内容 - 把导航链接单独抽出来,用
<nav></nav>包裹,并放在<header></header>内部或紧邻位置(顺序不重要,语义才重要) - 如果导航横跨 header 和 sidebar,就拆成两个
<nav></nav>,分别标注aria-label
侧边栏菜单必须用 <nav></nav>,而不是 <aside></aside>
后台系统里大量使用 <aside></aside> 包裹左侧菜单,理由常是“它在页面一侧”。但 <aside></aside> 的语义是“与主内容相关但可独立存在的补充内容”,比如广告、推荐列表、作者简介 —— 它不是导航。屏幕阅读器遇到 <aside></aside> 里的链接,不会提示“这是导航项”,用户无法快速识别操作路径。
真正该用的只有 <nav></nav>,哪怕它出现在页面左侧、折叠状态、或通过 JS 动态渲染。只要功能是“供用户跳转到不同模块”,就必须用 <nav></nav>。
额外提醒:
- 动态展开/收起的菜单,需同步更新
aria-expanded和aria-controls - 当前激活项要用
aria-current="page",别只靠 CSS 类名高亮 - 禁用状态的菜单项,设
aria-disabled="true"并移除tabindex,否则键盘用户仍能聚焦却无法触发
检查可访问性树是否生效的最快方式
改完 HTML 后别只看页面效果,直接打开 Chrome DevTools → Elements 面板 → 右键任意元素 → “Inspect Accessibility Properties”,或切换到 Accessibility 标签页。重点看三处:
- 导航区域是否显示
role="navigation",且 parent 是document或main等合法容器 - 每个链接是否在 accessibility tree 中有 name(来自
aria-label、<span></span>文本或title属性) - 没有意外出现
role="none"或aria-hidden="true"覆盖掉关键导航节点
最容易被忽略的是:后台系统常在 JS 初始化后动态插入导航 DOM,但没触发 aria-live 或焦点重置 —— 这会导致屏幕阅读器第一次加载时完全读不到菜单。必须在菜单渲染完成后,手动调用 element.focus() 或设置 tabindex="-1" 后再 focus,才能让 AT 感知到新结构。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











