:focus-visible 应用于需区分键盘Tab与鼠标点击焦点的场景;核心判断标准是是否需为键盘用户保留必要视觉反馈,同时避免鼠标用户看到冗余轮廓线。
直接用 :focus-visible 是最干净的方案,但浏览器支持不统一、polyfill 行为有坑,不能无脑上。
:focus-visible 而不是 :focus
核心判断标准:是否需要区分键盘 Tab 导航和鼠标点击触发的焦点。
:focus-visible
:focus-visible 正是为此设计:focus-visible 是当前事实标准focus-visible polyfill,但要注意它监听 keydown 的时机可能漏掉首次 Tab:focus-visible 的 CSS 写法和常见错误别直接覆盖所有 :focus,否则会破坏可访问性。正确写法是分层控制:
.js-focus-visible:focus:not(.focus-visible) { outline: none; }
:focus-visible, .focus-visible { outline: 2px solid #2196F3; outline-offset: 2px; }
class="js-focus-visible",否则第一行规则不生效input:focus { outline: none; } 单独一行——这会让键盘用户彻底失去焦点提示/* ✅ 推荐基础组合 */.js-focus-visible:focus:not(.focus-visible) { outline: none;}:focus-visible,.focus-visible { outline: 2px solid #2196F3; outline-offset: 2px;}
Safari 15.4+ 原生支持 :focus-visible,但 iOS 上仍有两个现实问题:
立即学习“前端免费学习笔记(深入)”;
input 获得焦点时可能不触发 :focus-visible(尤其在 textarea 或带 autofocus 的场景)@supports 检测,加一层 input:focus { outline: 2px solid #2196F3; } 在 @supports not (:focus-visible) 块里更稳妥select 上只依赖 :focus-visible;iOS Safari 对下拉框的焦点事件捕获不稳定,建议同时设 select:focus { box-shadow: 0 0 0 2px rgba(33,150,243,0.3); }
用了 focus-visible npm 包或 CDN 后,必须手动检查三件事:
<html> 标签是否自动加上了 class="js-focus-visible"?没加 = polyfill 没启动class="focus-visible"?没加 = 事件监听失败(常见于脚本加载过晚或被 defer 阻塞)focus-visible 类?如果有,说明 polyfill 的 hadKeyboardEvent 状态未重置,可能是页面有其他脚本干扰了 mousedown 事件流最易被忽略的是 polyfill 的初始化时机——它必须在 DOM ready 后立即执行,且不能被模块打包器误删(比如 Webpack 的 tree-shaking 把 import 'focus-visible' 当作无副作用代码干掉了)。