:focus-visible 仅在键盘导航(如Tab键)触发焦点时生效,不响应鼠标点击、触摸或JavaScript聚焦;浏览器通过UA启发式自动判断,无需手动维护状态。

用 :focus-visible 判断键盘触发的焦点
:focus-visible 是目前唯一原生支持“仅在键盘聚焦时生效”的 CSS 伪类。它不会在鼠标点击、触摸 tap 或 el.focus() 调用后激活,只响应 Tab、Shift+Tab、Ctrl+Tab 等键盘导航行为。
浏览器内部通过用户代理(UA)启发式判断——比如是否刚发生过鼠标事件、是否处于“指针模式”等,而非依赖 JS 标志位。这意味着你不需要手动维护状态,也避开了 event.detail === 0 这类不可靠检测。
注意:该伪类默认行为受 focus-visible UA 样式影响,部分旧版 Chromium(
:focus 和 :focus-visible 必须配合使用
单独写 :focus-visible 样式会导致鼠标点击后完全无焦点反馈,破坏可访问性。正确做法是先用 :focus 提供基础焦点样式,再用 :focus-visible 覆盖键盘专属表现:
立即学习“前端免费学习笔记(深入)”;
button:focus {
outline: 2px solid #999;
}
button:focus-visible {
outline: 2px solid #0066cc;
outline-offset: 2px;
}
这样既保证所有聚焦方式都有视觉反馈,又让键盘用户的焦点环更醒目、更符合无障碍规范。
常见错误包括:
- 只写
:focus-visible,鼠标点击按钮后没轮廓 - 把
:focus-visible写在:focus前面,被后者覆盖失效 - 用
outline: none直接干掉所有焦点样式,再没补救
兼容性兜底方案:不能只靠 :focus-visible
如果你需要支持 Safari 15.3 或 Firefox 99 以下版本,得手动模拟 :focus-visible 行为:
- 监听
mousedown和touchstart,设全局标记isPointerEvent = true - 在
focusin中检查该标记,若为true则给元素加data-focus-source="click",否则加data-focus-source="keyboard" - 用 CSS 选择器
[data-focus-source="keyboard"]:focus替代:focus-visible -
focusout里重置标记,防止跨元素残留
这个 JS 方案虽可行,但引入了状态管理复杂度,且在快速连续点击+Tab 时仍有竞态风险。优先升级到现代浏览器环境,再考虑降级。
为什么不用 event.detail 或 document.activeElement 判断?
event.detail 在 Chrome/Firefox 中对点击始终为 1,但对 el.focus()、屏幕阅读器操作、甚至某些快捷键触发却为 0,无法稳定区分“键盘 vs 鼠标”。
document.activeElement 本身不携带触发源信息;靠它前后对比是否伴随 mousedown 事件,会漏掉触摸设备、误判 iframe 内焦点切换、且无法处理合成事件(如 React 的 onFocus)。
这些方法都绕过了浏览器原生焦点判定逻辑,属于“自己造轮子”,而 :focus-visible 是 UA 已验证过的路径,更轻量、更可靠。
真正难的不是写对样式,而是意识到:焦点样式不是“要不要加”的问题,而是“谁在用、怎么用、什么时候该不同”的问题。别让键盘用户在你的页面上迷路。


















