You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

键盘可访问导航菜单:如何设置Tab键仅聚焦顶级菜单项

键盘可访问导航菜单实现方案

核心思路靠两个机制配合就能实现要求的交互:一是通过tabindex属性控制元素是否进入Tab默认焦点序列,二是绑定自定义键盘事件接管方向键的焦点移动逻辑。

核心实现规则

  • 默认状态下,所有子菜单内的可交互链接统一设置tabindex="-1",这类元素不会被Tab键遍历到,只有顶级导航链接保持tabindex="0",纳入默认Tab焦点流。
  • 给顶级导航项绑定键盘监听事件:当焦点停在顶级项、用户按下向下方向键时,展开对应子菜单,把子菜单第一个链接的tabindex改为0,同时直接将焦点移到该链接上。
  • 补充子菜单内的键盘交互适配:子菜单内按上下方向键可在同组子项间循环切换焦点,按向上方向键到第一个子项时可回退焦点到顶级链接,按ESC键收起子菜单并将焦点切回对应顶级项。
  • 焦点或点击事件触发到导航区域外时,自动收起所有展开的子菜单,将所有子菜单项的tabindex重置为-1,避免下次Tab导航出现异常焦点跳转。

代码示例

HTML结构

<nav class="main-nav">
  <ul class="nav-list">
    <li class="nav-item has-submenu">
      <a href="/products" class="nav-top-link" tabindex="0" aria-expanded="false">产品中心</a>
      <ul class="submenu" role="menu">
        <li role="none"><a href="/products/laptop" role="menuitem" class="submenu-link" tabindex="-1">笔记本电脑</a></li>
        <li role="none"><a href="/products/phone" role="menuitem" class="submenu-link" tabindex="-1">手机</a></li>
        <li role="none"><a href="/products/tablet" role="menuitem" class="submenu-link" tabindex="-1">平板</a></li>
      </ul>
    </li>
    <li class="nav-item has-submenu">
      <a href="/support" class="nav-top-link" tabindex="0" aria-expanded="false">技术支持</a>
      <ul class="submenu" role="menu">
        <li role="none"><a href="/support/faq" role="menuitem" class="submenu-link" tabindex="-1">常见问题</a></li>
        <li role="none"><a href="/support/contact" role="menuitem" class="submenu-link" tabindex="-1">联系我们</a></li>
      </ul>
    </li>
    <li class="nav-item">
      <a href="/about" class="nav-top-link" tabindex="0">关于我们</a>
    </li>
  </ul>
</nav>

核心JS逻辑

const topLinks = document.querySelectorAll('.nav-top-link');

// 重置所有子菜单状态
function resetAllSubmenus() {
  document.querySelectorAll('.submenu').forEach(menu => {
    menu.classList.remove('is-open');
    const parentLink = menu.previousElementSibling;
    if (parentLink) parentLink.setAttribute('aria-expanded', 'false');
    menu.querySelectorAll('.submenu-link').forEach(link => {
      link.tabIndex = -1;
    });
  });
}

// 绑定子菜单内部键盘事件
function bindSubmenuKeyEvent(submenu, parentTopLink) {
  const subLinks = Array.from(submenu.querySelectorAll('.submenu-link'));
  submenu.addEventListener('keydown', (e) => {
    const currentIndex = subLinks.indexOf(document.activeElement);
    switch(e.key) {
      case 'ArrowDown':
        e.preventDefault();
        const nextIndex = (currentIndex + 1) % subLinks.length;
        subLinks.forEach(link => link.tabIndex = -1);
        subLinks[nextIndex].tabIndex = 0;
        subLinks[nextIndex].focus();
        break;
      case 'ArrowUp':
        e.preventDefault();
        if (currentIndex === 0) {
          resetAllSubmenus();
          parentTopLink.focus();
          return;
        }
        const prevIndex = currentIndex - 1;
        subLinks.forEach(link => link.tabIndex = -1);
        subLinks[prevIndex].tabIndex = 0;
        subLinks[prevIndex].focus();
        break;
      case 'Escape':
        e.preventDefault();
        resetAllSubmenus();
        parentTopLink.focus();
        break;
    }
  });
}

// 顶级项键盘事件绑定
topLinks.forEach(link => {
  link.addEventListener('keydown', (e) => {
    const submenu = link.nextElementSibling;
    if (e.key === 'ArrowDown' && submenu?.classList.contains('submenu')) {
      e.preventDefault();
      resetAllSubmenus();
      submenu.classList.add('is-open');
      link.setAttribute('aria-expanded', 'true');
      const firstSubLink = submenu.querySelector('.submenu-link');
      firstSubLink.tabIndex = 0;
      firstSubLink.focus();
      bindSubmenuKeyEvent(submenu, link);
    }
  });
});

// 点击导航外部时重置菜单
document.addEventListener('click', (e) => {
  if (!e.target.closest('.main-nav')) resetAllSubmenus();
});

注意事项

  • 不要仅靠display: none控制子菜单隐藏来隔离焦点,部分辅助技术仍可能识别到隐藏的可交互元素,必须配合tabindex="-1"才能真正把子项移出默认Tab序列。
  • 顶级链接如果配置了实际跳转地址,不要拦截回车、空格键的默认触发事件,保证基础跳转功能正常。
  • 子菜单展开/收起时同步更新aria-expanded等无障碍属性,保证屏幕阅读器能正确识别菜单状态。

内容的提问来源于stack exchange,提问作者melissa

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 14:06:29