导航菜单必须支持tab键跳转,使用语义化标签(如、)或tabindex="0"确保可聚焦;下拉菜单需用aria-expanded、aria-haspopup、role="menu"和role="menuitem"正确标注并管理焦点。

导航菜单必须支持 Tab 键顺序跳转
键盘用户依赖 Tab 键在可聚焦元素间移动,如果菜单项不是原生可聚焦的(比如 <div> 或 <code><span></span>),就会被跳过。必须用语义化标签或显式添加 tabindex="0"。
推荐直接使用 <a></a>(有 href)或 <button></button> 作为菜单触发器和条目容器——它们默认可聚焦、可键盘激活,且无需额外 tabindex。避免给 <li> 或 <nav></nav> 加 tabindex="0",这会破坏自然焦点流。
- 主菜单项如果是纯展示(无跳转/无交互),至少加
tabindex="0"并绑定Enter/Space事件 - 下拉子菜单应延迟显示(如 hover + focus 同时触发),但不能只靠
:hover实现——键盘用户没有 hover - 确保
Tab进入子菜单后,能用↑/↓键在子项间循环,Esc关闭并返回父项
用 aria-expanded 和 aria-haspopup 标注折叠状态
屏幕阅读器需要知道某个菜单项是否可展开。仅靠视觉箭头或 CSS 类无法传达这个信息。
对带下拉的菜单项(如 <button>产品</button>),必须同时设置:
-
aria-haspopup="menu"(表示它关联一个菜单) -
aria-expanded="false"(初始关闭)或"true"(展开时动态更新) - 对应子菜单容器需有
role="menu",子项用role="menuitem"
漏掉任一属性,NVDA 或 VoiceOver 就可能读作“按钮”,而不提示“可展开”或“已展开”。
子菜单必须用 role="menu" 而不是 role="list"
很多开发者误以为下拉列表只是普通列表,于是套用 role="list" + role="listitem"。这是错的——它会让屏幕阅读器按列表逻辑朗读(“第1项,第2项…”),丢失菜单语义和快捷键支持。
如果你了解HTML,CSS和JavaScript,您已经拥有所需的工具开发Android应用程序。本动手本书展示了如何使用这些开源web标准设计和建造,可适应任何Android设备的应用程序 - 无需使用Java。您将学习如何创建一个在您选择的平台的Android友好的网络应用程序,然后转换与自由PhoneGap框架到一个原生的Android应用程序。了解为什么设备无关的移动应用是未来的潮流,并开始构建应用程序,提供更
role="menu" 触发的是菜单模式(Menu Mode),用户可直接用方向键导航,且屏幕阅读器会读作“菜单”、“菜单项”。但注意:
-
role="menu"只适用于**应用式菜单**(如桌面软件风格),不适用于主导航栏中的多级链接菜单(此时更推荐role="navigation"+ 嵌套<ul></ul>) - 若用
role="menu",所有子项必须是role="menuitem",且禁用<a></a>的默认跳转(用event.preventDefault()),否则会破坏键盘导航流 - 移动端或简化场景中,优先考虑用
<details></details>+<summary></summary>,它原生支持键盘和无障碍,但兼容性略低(IE 不支持)
焦点管理不能只靠 CSS :focus-visible
:focus-visible 确实能区分鼠标点击和键盘聚焦,但它不解决焦点“丢失”问题——比如子菜单展开后,焦点没自动移到第一个子项,键盘用户就得连按多次 Tab 才能进去。
必须手动控制:
- 点击/空格展开子菜单后,立即用
firstChild.focus()或querySelector('[role="menuitem"]').focus()把焦点移入 - 关闭子菜单时(
Esc或点击外部),焦点应回到触发它的父按钮 - 避免用
outline: none全局移除焦点框;如需定制样式,用outline: 2px solid #0066cc+outline-offset: 2px保证可见性
最常被忽略的是:子菜单隐藏后未重置 aria-expanded,或焦点未归位,导致后续键盘操作完全脱节。这类问题在测试中只能靠真实键盘+屏幕阅读器暴露,自动化工具几乎扫不出来。










