role="menuitem"需配合role="menu"容器、tabindex管理及手动键盘事件处理才能实现完整菜单导航;孤立使用无效,必须用js实现方向键焦点切换、enter/space激活及esc关闭逻辑。

role="menuitem" 本身不自动支持键盘导航
光写 role="menuitem" 不会触发浏览器的菜单行为,也不会响应 ↑/↓、Enter 或 Space。它只是语义标记,必须配合 ARIA 菜单模式(menu / menubar)的完整结构 + 手动键盘事件处理才能工作。
常见错误是孤立地给一个 <li> 加 role="menuitem",结果屏幕阅读器能读出“菜单项”,但焦点卡住、方向键无效——因为缺少父容器 role="menu" 和必要的 tabindex 管理。
- 必须包裹在
role="menu"容器中(不能是role="list"或普通<ul></ul>) - 每个
menuitem需有tabindex="-1"(不可通过 Tab 进入,仅靠方向键切换) - 需用 JS 监听
keydown,手动实现焦点移动逻辑 - 激活行为(如点击)需显式绑定到
Enter和Space键
正确的 HTML 结构和 tabindex 设置
菜单容器必须是 role="menu",且自身有 tabindex="0" 以便首次获得焦点;子项用 role="menuitem" 并统一设 tabindex="-1":
<div role="menu" tabindex="0"> <div role="menuitem" tabindex="-1">新建文件</div> <div role="menuitem" tabindex="-1">打开</div> <div role="menuitem" tabindex="-1">保存</div> </div>
注意:不要用 <button></button> 或 <a></a> 套 menuitem —— 会破坏 ARIA 菜单语义。如果需要按钮样式,用 CSS 重置 <div> 即可。<ul>
<li>
<code>tabindex="0" 让容器可被 Tab 键聚焦(第一次进入菜单)
tabindex="-1" 允许 JS 用 .focus() 控制焦点,但阻止自然 Tab 流menuitem 初始状态必须只有一个有 tabindex="0"(通常是第一个),其余为 -1;焦点切换时动态更新必须手写方向键焦点管理逻辑
浏览器不会帮你做上下切换。你需要监听容器的 keydown,识别方向键后计算下一个索引,并调用 .focus():
文章转信息图。将文章/笔记转化为手机可读的 HTML 信息图,自动匹配视觉风格。触发场景:文章转图、笔记转图、信息图、转小红书图、做张图、可视化这篇文章、文生图。
const menu = document.querySelector('[role="menu"]');
const items = menu.querySelectorAll('[role="menuitem"]');
<p>menu.addEventListener('keydown', (e) => {
const currentIndex = Array.from(items).indexOf(document.activeElement);
let nextIndex;</p><p>if (e.key === 'ArrowDown') {
nextIndex = (currentIndex + 1) % items.length;
} else if (e.key === 'ArrowUp') {
nextIndex = (currentIndex - 1 + items.length) % items.length;
} else if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
document.activeElement.click(); // 触发点击逻辑
return;
} else {
return;
}</p><p>items[nextIndex].focus();
});</p>
关键点:
- 用
Array.from(items)转换 NodeList,避免indexOf失效 - 模运算(
% items.length)实现循环切换 -
e.preventDefault()阻止空格键滚动页面 - 不要依赖
click()模拟——确保每个menuitem有真实的click事件监听器
容易被忽略的可访问性细节
很多实现漏掉这几点,导致屏幕阅读器体验断裂或键盘用户迷失:
- 菜单展开/收起时,需用
aria-expanded="true/false"同步更新(如果它是下拉菜单的一部分) - 若菜单项含子菜单(
role="menuitempopup"),必须用aria-haspopup="menu"和aria-controls关联 - 焦点进入菜单后,应禁用页面其他可聚焦元素(用
inert或aria-hidden配合管理) - ESC 键必须关闭菜单并把焦点移回触发按钮(如果有的话)
- 移动端 Safari 对
role="menu"支持弱,建议 fallback 到原生<select></select>或简化交互
最常被跳过的其实是焦点返回逻辑:用户按 ESC 退出菜单后,焦点没回到原来的位置,等于切断了键盘流。这个细节一漏,整个菜单就变成「进得去、出不来」的陷阱。










