基于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
相关产品推荐
相关产品推荐

