
键盘焦点无障碍管理Focus Trap 焦点陷阱与 roving tabindex 架构实战在 Web 无障碍A11y领域有很大一部分用户包括运动障碍者、盲人用户、以及重度依赖键盘快捷键的极客开发者在浏览网页时完全不使用鼠标他们仅依靠键盘上的Tab、Shift Tab、Enter、Space和方向键进行全量交互。然而在很多前端组件库中键盘焦点管理却充斥着严重的体验灾难模态弹窗焦点穿透Focus Leakage弹窗打开后用户按Tab键焦点竟然顺着看不见的 DOM 树一路移动到了被弹窗遮挡在底层的背景网页链接上盲人用户完全迷失在屏幕之外Tab 地狱Tab Fatigue在包含 20 个工具图标的复杂工具栏Toolbar中用户必须把Tab键连续狂敲 20 次才能跳过这个工具栏到达主要内容区弹窗关闭后焦点丢失Focus Loss用户关闭弹窗后页面焦点突然被丢到了body根节点用户不得不重新从第一行开始重新按几十次 Tab。在 W3C WAI-ARIA 权威规范中Focus Trap焦点陷阱与Roving Tabindex巡回焦点索引算法是解决这两大难题的标准工业级解法。核心机制一Focus Trap模态焦点陷阱——闭环回环当模态对话框Modal / Dialog打开时系统必须遵循三大焦点铁律初始焦点锁定Initial Focus弹窗挂载完成的瞬间主动将焦点转移到弹窗内部的第一个可聚焦元素如确认按钮或输入框焦点物理回环Trap Loop当焦点处于弹窗内最后一个可聚焦元素时按下Tab焦点必须瞬间瞬移回弹窗的第一个元素当在第一个元素按下Shift Tab时反向瞬移到最后一个元素焦点原路归位Restore Focus弹窗关闭时必须把焦点精准送回打开该弹窗的那个原始触发按钮Trigger Button上。[弹窗开启] ── 记录触发按钮 triggerElement ── 焦点强制移入内部首项 Element 1 │ ▼ (用户按 Tab 键遍历) [Element 1] ──Tab── [Element 2] ──Tab── [Element 3 (末尾项)] ▲ │ └────────────────── Tab 强制回环 ──────────────┘// focus-trap-engine.ts 生产级焦点陷阱引擎 export class FocusTrapEngine { private container: HTMLElement; private previouslyFocusedElement: HTMLElement | null null; private focusableSelector a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex-1]) ; constructor(container: HTMLElement) { this.container container; } public activate() { // 1. 记录当前聚焦的触发器节点 this.previouslyFocusedElement document.activeElement as HTMLElement; // 2. 找到弹窗内所有可聚焦节点 const focusableNodes this.getFocusableElements(); if (focusableNodes.length 0) { focusableNodes[0].focus(); // 聚焦第一项 } // 3. 挂载键盘拦截监听器 this.container.addEventListener(keydown, this.handleKeyDown); } public deactivate() { this.container.removeEventListener(keydown, this.handleKeyDown); // 4. 焦点精准原路归位 if (this.previouslyFocusedElement typeof this.previouslyFocusedElement.focus function) { this.previouslyFocusedElement.focus(); } } private getFocusableElements(): HTMLElement[] { return Array.from(this.container.querySelectorAll(this.focusableSelector)); } private handleKeyDown (e: KeyboardEvent) { if (e.key ! Tab) return; const focusable this.getFocusableElements(); if (focusable.length 0) { e.preventDefault(); return; } const firstEl focusable[0]; const lastEl focusable[focusable.length - 1]; // Shift Tab 逆向到达首项时回环到末尾 if (e.shiftKey document.activeElement firstEl) { e.preventDefault(); lastEl.focus(); } // Tab 正向到达末尾时回环到首项 else if (!e.shiftKey document.activeElement lastEl) { e.preventDefault(); firstEl.focus(); } }; }核心机制二Roving Tabindex巡回焦点索引——复合组件救星在工具栏Toolbar、Tab 选项卡列表TabList或菜单Menu这类复合组件中W3C 规范要求整个复合组件作为一个整体只占用 1 个全局 Tab 停靠位用户按 1 次Tab进入工具栏再按 1 次Tab即可跳过整个工具栏进入下一个模块在工具栏内部改用方向键Arrow Left / Right / Up / Down进行子项穿梭Roving Tabindex 算法实现原理内部当前激活的子项设置tabindex0内部所有其他未激活子项全部设置tabindex-1当用户按方向键时将上一个子项置为-1将新目标子项置为0并立即调用.focus()// RovingTabList.tsx - 生产级巡回焦点选项卡 import React, { useState, useRef } from react; export const RovingTabList: React.FC () { const [activeIdx, setActiveIdx] useState(0); const tabRefs useRef(HTMLButtonElement | null)[]([]); const tabs [系统大盘, 视觉 Token, 无障碍审计, 发布配置]; const handleKeyDown (e: React.KeyboardEvent, index: number) { let nextIdx index; if (e.key ArrowRight || e.key ArrowDown) { e.preventDefault(); nextIdx (index 1) % tabs.length; } else if (e.key ArrowLeft || e.key ArrowUp) { e.preventDefault(); nextIdx (index - 1 tabs.length) % tabs.length; } else if (e.key Home) { e.preventDefault(); nextIdx 0; } else if (e.key End) { e.preventDefault(); nextIdx tabs.length - 1; } if (nextIdx ! index) { setActiveIdx(nextIdx); tabRefs.current[nextIdx]?.focus(); // 巡回聚焦 } }; return ( div roletablist aria-label设计系统工作区 classNameflex gap-2 p-1.5 bg-slate-900 rounded-2xl border border-slate-800 {tabs.map((tab, idx) { const isSelected activeIdx idx; return ( button key{tab} ref{(el) (tabRefs.current[idx] el)} roletab aria-selected{isSelected} // 核心: 仅激活项为 0其余全为 -1 tabIndex{isSelected ? 0 : -1} onClick{() setActiveIdx(idx)} onKeyDown{(e) handleKeyDown(e, idx)} className{px-4 py-2 text-xs font-semibold rounded-xl transition-all outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 ${isSelected ? bg-indigo-600 text-white shadow-lg shadow-indigo-600/30 : text-slate-400 hover:text-white}} {tab} /button ); })} /div ); };总结键盘焦点管理是 Web 无障碍交互的神经中枢。在模态弹窗中建立坚固的 Focus Trap 闭环在复合工具栏中践行 Roving Tabindex 的方向键巡回哲学你的前端组件库才能真正摆脱焦点穿透与键盘迷航的灾难为全球数以千万计的键盘操作者与读屏用户交付充满掌控力与尊严的丝滑交互体验。