Skip to content
返回质量保障

键盘导航

每个交互元素须仅用键盘可操作,带可见焦点指示和合理顺序。

为什么

键盘导航是无障碍基础——是运动障碍用户(不能用鼠标)的唯一输入、高级用户的最快输入、每个屏幕阅读器(用键盘导航)的前提。不能聚焦的按钮、困 tab 键的模态、需鼠标悬停的下拉——这些非小不便,是使应用对大量人口不可用的完全障碍。WCAG 2.1.1(A 级、最低法律要求)说"所有功能可通过键盘界面操作。"这非可选,是无障碍审计员测的第一件事。

何时使用

每个交互元素、每页。每个加或改交互元素的 PR 测键盘导航,发布前全过。

如何做

1. 仅用键盘测全流程——放下鼠标
   - Tab: 移到下个交互元素
   - Shift+Tab: 移到上个
   - Enter/Space: 激活按钮和链接
   - Escape: 关模态、菜单、下拉
   - 方向键: 复合 widget 内导航(标签、菜单、列表框)

2. 验证焦点指示可见
   - 每个聚焦元素须有可见 outline 或 border
   - 不用 outline: none 无替代(box-shadow、border)
   - 高对比模式模拟测——指示仍须可见
   - 用 :focus-visible 仅键盘指示(避免鼠标点击显)

3. 验证焦点顺序合理
   - Tab 应跟视觉阅读顺序(上到下、左到右)
   - 若视觉顺序与 DOM 顺序不同,修 DOM 非 tab 顺序
   - 用 tabindex="0" 使非原生元素可聚焦,但优先原生元素
   - 永不用 tabindex > 0(正值扰自然 tab 顺序)

4. 管理动态内容焦点
   - 模态开时,焦点移到模态(非后面触发器)
   - 模态关时,焦点返回触发器(用户可继续)
   - 模态开时困焦点在内(Tab 在模态内循环)
   - 用 aria-live 宣无焦点变更的动态内容

5. 修常见模式
   - <div onClick> → <button onClick>(原生按钮默认键盘可访问)
   - <a href="#"> → <a href="/real-url">(真链接键盘可访问)
   - 自定义 UI 的 <select> → 用支持键盘的库(Radix、Headless UI)
   - 悬停下拉 → 点击下拉(键盘不能悬停)

示例

// 坏: 带 onClick 的 div——不可聚焦、不可键盘访问
<div onClick={handleClick} className="button-like">点我</div>
// 屏幕阅读器: "点我"(无 role、无可用动作)
// 键盘: 不能 Tab 到、不能激活

// 好: 原生按钮——可聚焦、可键盘访问、正确 role
<button onClick={handleClick}>点我</button>
// 屏幕阅读器: "点我,按钮"
// 键盘: Tab 聚焦、Enter/Space 激活

// 模态焦点管理
import { useRef, useEffect } from "react";

function Modal({ isOpen, onClose, children }) {
  const modalRef = useRef(null);
  const triggerRef = useRef(null); // 开模态的按钮

  useEffect(() => {
    if (isOpen) {
      // 模态开时焦点移入
      modalRef.current?.focus();
    } else {
      // 关时焦点返回触发器
      triggerRef.current?.focus();
    }
  }, [isOpen]);

  if (!isOpen) return null;
  return (
    <div role="dialog" aria-modal="true" ref={modalRef} tabIndex={-1}>
      {children}
      <button onClick={onClose}>关</button>
    </div>
  );
}

// 可见焦点指示(勿移)
// CSS: 保留可见 outline 或用等效替代
.button:focus-visible {
  outline: 2px solid #0066cc;
  outline-offset: 2px;
}
/* 不要这样做: */
.button { outline: none; } /* 移键盘指示 */

// 测试: 放下鼠标 Tab 过整页
// 1. 能到每个按钮、链接和输入吗?
// 2. 每个焦点指示可见吗?
// 3. 能用 Enter/Space 激活按钮吗?
// 4. 能用 Escape 关模态吗?
// 5. 关后焦点返回触发器吗?

常见问题

  • ⚠因 div"更易样式"用 <div onClick> 而非 <button>——div 不可聚焦、不可键盘激活、不宣为按钮,排斥每个键盘和屏幕阅读器用户。
  • ⚠为美观移焦点 outline(outline: none)无替代——键盘用户不知在哪,Tab 过页是盲导航。
  • ⚠开模态不移焦点入——屏幕阅读器续读模态后页,键盘用户 Tab 过不可见元素。
  • ⚠用正 tabindex(tabindex="1")"修"tab 顺序——这从自然流移除元素创困惑不可预测导航顺序。

最佳实践

  • ✓尽可能用原生 HTML 元素(button、a、input、select)——默认键盘可访问和屏幕阅读器宣读,样式可叠加。
  • ✓每个交互元素保留可见焦点指示,用 :focus-visible 仅键盘导航时显——移它是最常见 a11y 回归。
  • ✓动态内容管理焦点: 模态开时移入、关时返回触发器、开时困在内——这是可用和不可用模态的差。
  • ✓每 PR 仅键盘过一遍(放下鼠标)——花 2 分钟抓最常见 a11y 障碍在发前。