React 可访问性实战:用 focus trap 与 ARIA 把弹窗做成无障碍组件
test12026-10-2418 次阅读
为什么可访问性不是加分项
很多团队的组件库只保证鼠标能点、键盘能敲,却忘了屏幕阅读器用户。一个没有焦点管理、没有 ARIA 语义的弹窗,对读屏软件来说就是一团乱序的文本。可访问性(a11y)的核心是把"视觉交互"翻译成"语义交互",让键盘和读屏器也能走通同一条路。
用 useFocusTrap 锁住焦点
弹窗打开时,Tab 键不应该跑出对话框;Esc 应该关闭它;打开时要主动把焦点移到弹窗内。下面这个 hook 处理了焦点捕获与还原。
import { useEffect, useRef } from "react";
function useFocusTrap(active: boolean) {
const containerRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (!active) return;
const container = containerRef.current;
if (!container) return;
const selector =
'a[href], button:not([disabled]), input, select, textarea, [tabindex]:not([tabindex="-1"])';
const focusables = () =>
Array.from(container.querySelectorAll<HTMLElement>(selector)).filter(
(el) => el.offsetParent !== null
);
const prevFocus = document.activeElement as HTMLElement | null;
const first = focusables()[0];
first?.focus();
const onKey = (e: KeyboardEvent) => {
if (e.key === "Escape") {
container.dispatchEvent(new CustomEvent("dialog-close"));
return;
}
if (e.key !== "Tab") return;
const items = focusables();
if (items.length === 0) return;
const firstEl = items[0];
const lastEl = items[items.length - 1];
if (e.shiftKey && document.activeElement === firstEl) {
e.preventDefault();
lastEl.focus();
} else if (!e.shiftKey && document.activeElement === lastEl) {
e.preventDefault();
firstEl.focus();
}
};
container.addEventListener("keydown", onKey);
return () => {
container.removeEventListener("keydown", onKey);
prevFocus?.focus();
};
}, [active]);
return containerRef;
}
给弹窗补上 ARIA 语义
光有焦点还不够,读屏器需要知道"这是一个对话"。role、aria-modal、aria-labelledby 三件套把结构讲清楚。
export function Dialog({ open, title, onClose, children }: DialogProps) {
const ref = useFocusTrap(open);
if (!open) return null;
return (
<div className="overlay" onClick={onClose}>
<div
ref={ref}
role="dialog"
aria-modal="true"
aria-labelledby="dialog-title"
className="dialog"
onClick={(e) => e.stopPropagation()}
onDialogClose={onClose}
>
<h2 id="dialog-title">{title}</h2>
<div>{children}</div>
</div>
</div>
);
}
把错误用 aria-live 播报出来
表单校验失败时,别只靠红色文字。加一段 aria-live="polite" 的区域,读屏器会自动把错误念出来。
<p aria-live="polite" className="error">{error ?? ""}</p>
做好这几步,弹窗对键盘用户和读屏器都可用。可访问性不是事后补丁,而是组件设计的第一公民。
T
test1
文章作者
为什么可访问性不是加分项 很多团队的组件库只保证鼠标能点、键盘能敲,却忘了屏幕阅读器用户。一个没有焦点管理、没有 AR...
- 分类
- 技术
- 发布时间
- 2026-10-24
- 字数
- 约 2555 字
- 阅读
- 18 次
