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

基于Material UI的React可访问Tooltip组件焦点顺序问题咨询

针对默认打开的MUI可访问Tooltip解决方案

1. 修正DOM结构关联,解决焦点顺序问题

由于Tooltip默认挂载到document.body导致和按钮DOM脱节,焦点逻辑混乱。可以利用MUI的Portal组件,将Tooltip挂载到按钮的父元素下,让两者DOM结构关联,浏览器默认焦点顺序会更符合逻辑:

import { Portal } from '@mui/material';
import { useState, useRef, cloneElement } from 'react';

const CustomTooltip = ({ children, open, onClose, title }) => {
  const [containerEl, setContainerEl] = useState(null);
  const buttonRef = useRef(null);

  const setButtonRef = (el) => {
    buttonRef.current = el;
    if (el) setContainerEl(el.parentElement);
  };

  return (
    <>
      {cloneElement(children, { ref: setButtonRef })}
      {open && (
        <Portal container={containerEl}>
          <div className="tooltip-container" role="tooltip" id="tooltip-content">
            <div tabIndex={0} className="tooltip-text">
              {title}
            </div>
            <button 
              onClick={onClose} 
              aria-label="关闭提示框"
              className="tooltip-close-btn"
            >
              X
            </button>
          </div>
        </Portal>
      )}
    </>
  );
};

2. 调整无障碍播报优先级

要让屏幕阅读器优先读取Tooltip内容而非按钮的aria-label,可以做以下处理:

  • 组件挂载且Tooltip默认打开时,主动将焦点设置到Tooltip的可聚焦文本上,屏幕阅读器会优先播报焦点元素内容:
const tooltipTextRef = useRef(null);

useEffect(() => {
  if (open) {
    tooltipTextRef.current?.focus();
  }
}, [open]);

// 给Tooltip文本绑定ref
<div ref={tooltipTextRef} tabIndex={0} className="tooltip-text">
  {title}
</div>
  • 给按钮设置aria-describedby关联Tooltip的ID,同时在Tooltip打开时移除按钮的aria-label,避免播报冲突:
{cloneElement(children, {
  ref: setButtonRef,
  'aria-describedby': open ? 'tooltip-content' : undefined,
  'aria-label': open ? undefined : children.props['aria-label']
})}

3. 实现Tooltip内焦点循环

因为Tooltip包含两个可聚焦元素,需要让焦点在内部循环,避免用户按Tab跳出到页面其他元素:

const closeButtonRef = useRef(null);

const handleTooltipKeyDown = (e) => {
  const focusableEls = [tooltipTextRef.current, closeButtonRef.current];
  const currentFocusIndex = focusableEls.indexOf(document.activeElement);

  if (e.key === 'Tab') {
    e.preventDefault();
    const nextIndex = e.shiftKey 
      ? (currentFocusIndex - 1 + focusableEls.length) % focusableEls.length 
      : (currentFocusIndex + 1) % focusableEls.length;
    focusableEls[nextIndex].focus();
  }
};

// 在Tooltip容器上绑定键盘事件
<div 
  className="tooltip-container" 
  role="tooltip" 
  id="tooltip-content"
  onKeyDown={handleTooltipKeyDown}
>
  <div ref={tooltipTextRef} tabIndex={0} className="tooltip-text">
    {title}
  </div>
  <button 
    ref={closeButtonRef}
    onClick={() => {
      onClose();
      buttonRef.current?.focus();
    }} 
    aria-label="关闭提示框"
    className="tooltip-close-btn"
  >
    X
  </button>
</div>

关闭Tooltip时,要把焦点归还到触发按钮,保证焦点不丢失。

4. 补充无障碍细节

  • 确保Tooltip的role="tooltip"正确设置,配合aria-describedby建立和按钮的语义关联
  • 关闭按钮必须设置清晰的aria-label,比如"关闭提示框",避免屏幕阅读器只播报"X"
  • 可聚焦的Tooltip文本添加tabIndex={0}后,要添加:focus状态的样式,让键盘用户明确当前焦点位置

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 02:22:29