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

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











