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

React MUI菜单按TAB键自动关闭,如何实现可TAB导航的无障碍巨菜单?

解决Material-UI巨菜单(MegaMenu)的TAB键导航无障碍问题

MUI Menu组件默认会在按下TAB键时触发关闭逻辑,这不符合巨菜单的无障碍需求——我们需要让用户能在菜单内部通过TAB遍历所有链接,同时保留ENTER键选中链接、ESC键关闭菜单的交互。以下是具体的实现方案:

核心思路

  1. 拦截Menu组件的TAB键事件,阻止默认的关闭行为;
  2. 手动管理菜单内可聚焦元素的焦点移动,支持正向/反向TAB遍历;
  3. 补充无障碍细节,比如菜单打开时自动聚焦第一个可交互元素。

实现代码示例

import React, { useRef, useEffect } from 'react';
import { Menu, MenuItem, Button } from '@mui/material';

const MegaMenu = () => {
  const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
  const menuRef = useRef<HTMLDivElement>(null);
  const focusableElementsRef = useRef<HTMLElement[]>([]);

  const open = Boolean(anchorEl);

  const handleClick = (event: React.MouseEvent<HTMLElement>) => {
    setAnchorEl(event.currentTarget);
  };

  const handleClose = () => {
    setAnchorEl(null);
  };

  // 收集菜单内所有可聚焦元素
  useEffect(() => {
    if (menuRef.current) {
      focusableElementsRef.current = Array.from(
        menuRef.current.querySelectorAll(
          'a, button, input, select, textarea, [tabindex]:not([tabindex="-1"])'
        )
      ) as HTMLElement[];
    }
  }, [open]);

  // 菜单打开时自动聚焦第一个可交互元素
  useEffect(() => {
    if (open && focusableElementsRef.current.length > 0) {
      focusableElementsRef.current[0].focus();
    }
  }, [open]);

  const handleMenuKeyDown = (event: React.KeyboardEvent) => {
    const { key, shiftKey } = event;
    const currentFocusIndex = focusableElementsRef.current.findIndex(
      el => el === document.activeElement
    );

    if (key === 'Tab') {
      event.preventDefault(); // 阻止MUI默认的TAB关闭行为

      if (shiftKey) {
        // SHIFT+TAB:切换到上一个可聚焦元素,循环到末尾
        const targetIndex = currentFocusIndex > 0 
          ? currentFocusIndex - 1 
          : focusableElementsRef.current.length - 1;
        focusableElementsRef.current[targetIndex].focus();
      } else {
        // TAB:切换到下一个可聚焦元素,循环到开头
        const targetIndex = currentFocusIndex < focusableElementsRef.current.length - 1 
          ? currentFocusIndex + 1 
          : 0;
        focusableElementsRef.current[targetIndex].focus();
      }
    } else if (key === 'Escape') {
      // 保留ESC键关闭菜单的默认交互
      handleClose();
    }
  };

  return (
    <div>
      <Button
        id="mega-menu-trigger"
        aria-controls={open ? 'full-width-mega-menu' : undefined}
        aria-haspopup="true"
        aria-expanded={open ? 'true' : undefined}
        onClick={handleClick}
      >
        打开全宽巨菜单
      </Button>
      <Menu
        id="full-width-mega-menu"
        anchorEl={anchorEl}
        open={open}
        onClose={handleClose}
        onKeyDown={handleMenuKeyDown}
        ref={menuRef}
        PaperProps={{
          sx: { width: '100%', maxWidth: 'none', padding: '16px' }
        }}
      >
        <div style={{ display: 'flex', gap: '24px' }}>
          <div>
            <h3 style={{ margin: '0 0 8px', fontSize: '16px' }}>导航组1</h3>
            <a href="/home" tabIndex="0" style={{ display: 'block', margin: '4px 0' }}>首页</a>
            <a href="/about" tabIndex="0" style={{ display: 'block', margin: '4px 0' }}>关于我们</a>
            <a href="/contact" tabIndex="0" style={{ display: 'block', margin: '4px 0' }}>联系我们</a>
          </div>
          <div>
            <h3 style={{ margin: '0 0 8px', fontSize: '16px' }}>导航组2</h3>
            <a href="/products" tabIndex="0" style={{ display: 'block', margin: '4px 0' }}>产品列表</a>
            <a href="/services" tabIndex="0" style={{ display: 'block', margin: '4px 0' }}>服务介绍</a>
          </div>
        </div>
      </Menu>
    </div>
  );
};

export default MegaMenu;

关键细节说明

  • 阻止默认TAB行为:通过onKeyDown事件监听TAB键,调用event.preventDefault()避免菜单关闭;
  • 焦点管理:收集菜单内所有可聚焦元素,手动处理TAB/Shift+TAB的焦点切换,支持循环遍历;
  • 无障碍优化:菜单打开时自动聚焦第一个元素,同时保留ESC键关闭菜单的标准交互;
  • 全宽样式:通过PaperProps.sx设置菜单宽度为100%,实现全宽巨菜单效果。

额外无障碍建议

  • 给所有可交互元素添加清晰的aria-label或关联aria-labelledby,方便屏幕阅读器识别;
  • 确保菜单触发按钮有正确的aria属性(如示例中的aria-controls、aria-expanded);
  • 链接被选中时(ENTER键),完成跳转后自动关闭菜单,保持交互一致性。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 03:26:09