Prompts-MCP 686 skills · ai / design / design-pattern / framework / fundamentals / habit / lang / tech-selection /mcp/sse
framework framework/react/a11y/keyboard-focus.md high

react-a11y-keyboard-focus

React 键盘可达与焦点管理:交互元素键盘可操作 / 可见焦点环 / tabIndex 指南 / 弹窗焦点陷阱与回焦。Use when 自研浮层做焦点陷阱 / 自定义控件加键盘操作 / 调 tabIndex / 弹窗关闭还焦 / 去掉焦点环

tabindexfocus trap焦点陷阱aria-modal键盘可达onkeydownescape焦点环
paths
  • frontend/src/**/*.tsx
  • frontend/src/**/*.ts

React · 键盘可达与焦点管理

规则

决策点:这个交互元素能否只用键盘完成全部操作,且焦点始终可见、可还原?

场景 做法
交互元素(按钮 / 链接 / 自定义控件) 键盘可达:Enter/Space 激活、Esc 关闭浮层
焦点可见 保留焦点环;若去 outline 必须补 :focus-visible 替代样式
自定义控件纳入 Tab 序 tabIndex={0}
程序聚焦但不进 Tab 序 tabIndex={-1}(如 dialog 容器)
正数 tabIndex 禁用——打乱自然顺序
antd Modal / Drawer 默认已捕获焦点+关闭回焦,别重造
自研浮层 自己 trap focus,关闭时把焦点还给触发元素
dialog 挂载 聚焦容器 + role="dialog" + aria-modal="true"

反例

// ❌ 全局去焦点环,键盘用户看不到光标在哪
*:focus { outline: none; }

// ❌ 自定义下拉键盘不可达:div 无 tabIndex、无键盘事件、无 role
<div className="dropdown" onClick={toggle}>选择</div>

正例

import { Button } from "antd";

// 自定义可聚焦控件:纳入 Tab 序 + 键盘激活 + 语义角色
function ToggleChip({ label, onActivate }: { label: string; onActivate: () => void }) {
  // 键盘激活:Enter/Space 等价于点击
  const handleKeyDown = (e: React.KeyboardEvent) => activateOnEnterSpace(e, onActivate);
  return (
    <span
      role="button"
      tabIndex={0}                      // 纳入 Tab 
      className="chip"                  // CSS 里用 :focus-visible 画焦点环
      onClick={onActivate}
      onKeyDown={handleKeyDown}
    >
      {label}
    </span>
  );
}
// 自研浮层:trap focus + Esc 关闭 + 关闭回焦触发元素
function FocusTrapPanel({ open, onClose, triggerRef, children }: FocusTrapPanelProps) {
  const containerRef = useRef<HTMLDivElement>(null);

  // 打开时:把焦点移入容器;关闭时:还焦给触发元素
  useEffect(() => {
    if (!open) return;
    containerRef.current?.focus();
    return () => triggerRef.current?.focus();
  }, [open, triggerRef]);

  // Esc 关闭 + Tab 在浮层内循环(逻辑下沉到 hook)
  const handleKeyDown = useFocusTrap(containerRef, { onEscape: onClose });

  if (!open) return null;
  return (
    <div
      ref={containerRef}
      role="dialog"
      aria-modal="true"                 // 屏幕阅读器进入模态语境
      tabIndex={-1}                     // 程序可聚焦,不进 Tab 
      onKeyDown={handleKeyDown}
    >
      {children}
    </div>
  );
}

activateOnEnterSpace / useFocusTrap 是下沉的纯函数 / hook,组件体只做编排。简单确认 / 复杂弹窗优先用 antd(焦点陷阱与回焦内建);动效配合 prefers-reduced-motion 降级,见 ../gsap/reduced-motion.md

自检

  • [ ] 每个交互元素仅用键盘可完成操作(Enter/Space 激活、Esc 关闭)?
  • [ ] 焦点环可见,没有裸 outline:none 而不补 :focus-visible
  • [ ] 自定义控件 tabIndex={0}、程序聚焦容器 tabIndex={-1}、无正数 tabIndex?
  • [ ] 弹窗优先用 antd Modal/Drawer,没有重造焦点陷阱?
  • [ ] 自研浮层 trap focus,关闭时焦点还给触发元素?
  • [ ] dialog 容器 role="dialog" + aria-modal="true" 且挂载时聚焦?

相关