键盘导航
每个交互元素须仅用键盘可操作,带可见焦点指示和合理顺序。
为什么
键盘导航是无障碍基础——是运动障碍用户(不能用鼠标)的唯一输入、高级用户的最快输入、每个屏幕阅读器(用键盘导航)的前提。不能聚焦的按钮、困 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 障碍在发前。