子菜单必须嵌套在父<li>内部,且HTML lang属性须为zh-CN或zh-TW;导航需用aria-label明确功能,菜单项须用而非<div>,面包屑应使用<ol>并标注aria-current="page"。

子菜单必须嵌在父
多级菜单的无障碍基础不是 CSS 显隐或 JS 交互,而是 DOM 结构本身是否反映真实层级关系。如果把二级
- 拆出来放在页面底部、靠
- 正确结构:每个子
- 必须是其父
- 的最后一个子节点,即
<li>Services<ul>...</ul></li> - 用
class="has-submenu"标记可展开项,便于 CSS 和 JS 区分有无子菜单 - 避免用
aria-owns强行绑定——它只是兜底方案,需要手动接管焦点流,极易出错
导航容器必须带 aria-label,且值要具体
<nav>标签本身只说明“这是个导航区域”,不说明“这是干什么用的”。当页面存在多个<nav>(比如顶部主导航 + 侧边栏 + 面包屑),屏幕阅读器用户按 N 键跳转时,只会听到“导航”,根本分不清哪个是主菜单。- 禁止写
<nav aria-label="Navigation">或干脆不写aria-label - 必须按实际功能命名:如
<nav aria-label="主导航">、<nav aria-label="页内目录"> - 面包屑建议用
<ol>而非<ul>,并给当前页链接加aria-current="page"
菜单项要用语义化交互元素,别用 div + click
把
<div onclick="...">当菜单项,等于主动放弃键盘支持和屏幕阅读器识别。浏览器不会给div分配 tab 顺序,也不会告诉辅助技术“这是可点击的菜单项”。立即学习“前端免费学习笔记(深入)”;
- 一级和二级菜单项统一用
<a href="#">,即使跳转逻辑由 JS 控制,也要保留href保证可聚焦 - 当前页链接加
aria-current="page",不要用aria-disabled="true"或移除href - 禁用项(如灰掉的“下一页”)才用
aria-disabled="true"+tabindex="-1",并同步视觉样式 - 绝对不要用
<div role="menuitem">模拟——缺少原生焦点管理、Enter/Space 响应、状态反馈
lang 属性缺失或写错,会让中文菜单读成日文或乱码
菜单里全是中文,但
<html>没写lang,或者写成lang="zh"、lang="ch",VoiceOver/NVDA 就可能切到日语引擎朗读,声调错乱,用户听不懂。- 必须写全 IETF 标准值:
<html lang="zh-CN">(简体)、<html lang="zh-TW">(繁体) - 局部语言切换用嵌套
lang,如英文术语:<span lang="en">SEO</span> - React/Vue 动态渲染时,确保 SSR 输出的初始 HTML 就带对
lang,别靠 JS 后补
lang属性随手写个zh就交差,结果中文菜单被读成外语。这两处不改,其他所有 ARIA 和 JS 优化都白搭。 - 的最后一个子节点,即
position: absolute 定位“飞”过去,屏幕阅读器就完全无法识别父子关系,键盘 Tab 会直接跳过整个子菜单。
常见错误写法:<li>Services</li><ul class="submenu">...</ul>——这会让子菜单变成同级兄弟节点,:hover 或 :checked 选择器也匹配不到正确上下文。



















