你以为点完按钮看到那个框是 CSS 的 bug?今天 :focus-visible 把这件事彻底修了
你有没有被设计师质问过:「这个按钮点击后为什么有个蓝框?」然后你查了半天代码,发现没有任何地方写了这个框,CSS 里也没有。这不是 bug——是浏览器的默认 focus 环。
问题是,这个环对鼠标用户来说是噪音,对键盘用户来说是唯一的导航指示器。以前你只能靠 outline: none 删掉它,然后祈祷键盘用户不需要知道焦点在哪儿。
:focus-visible 就是来解决这个历史遗留问题的。
这个 CSS 伪类只在浏览器判断「用户真的需要看到焦点在哪儿」的时候才触发。换句话说:鼠标点按钮,不显示;Tab 键盘跳过去,显示。输入框例外——光标本身就算焦点指示,所以点击输入框也会触发。
三行代码把这个问题彻底结了:
button:focus { outline: none; }
button:focus-visible { outline: 3px solid #2563eb; outline-offset: 2px; }
鼠标用户点按钮干干净净,键盘用户 Tab 进来清清楚楚。两个需求同时满足,不需要任何 JavaScript。
浏览器怎么判断该不该显示
这不是随机的,浏览器有一套内置启发规则:
显示 focus 环的场景:
- Tab 键导航到达元素
- 方向键在组内移动
- 屏幕阅读器的虚拟焦点
- 程序化调用
.focus(),且用户最近在用键盘
不显示 focus 环的场景:
- 鼠标点击普通按钮或链接
- 触摸点击移动端(手机用户不需要持久焦点环)
- 程序化调用
.focus(),但用户最近在用鼠标
输入框永远显示,因为用户需要知道自己在哪个字段里打字。
WCAG 合规现在是法律问题
outline: none 删掉焦点环而不提供替代样式,在 WCAG 2.4.7(Level AA)里是明确的合规失败。键盘用户、开关设备用户、屏幕阅读器用户——他们的「鼠标」就是焦点环,删掉了等于把他们的光标关掉。
欧盟的无障碍法案(European Accessibility Act)从 2025 年 6 月 28 日起要求商业产品和服务达到 WCAG 2.1 AA 合规,美国 ADA Title II 也有类似要求。不是可做可不做的最佳实践,是法律层面的强制要求。
outline 才是正确的工具,不是 box-shadow
有些工程师喜欢用 box-shadow 做焦点环,因为视觉效果更柔和。但 box-shadow 在 Windows 高对比度模式下完全不可见——这个模式下只有 outline 被渲染。正确做法是用 outline 保证合规,box-shadow 当作额外视觉增强。
button:focus-visible {
outline: 3px solid #2563eb;
outline-offset: 2px;
/* 增强:柔和光晕,Windows HCM 下自动降级到 outline */
box-shadow: 0 0 0 6px rgba(37, 99, 235, 0.2);
}
outline-offset: 2px 这个属性很多人忽略——它让焦点环和元素边框之间留出呼吸空间,视觉上清晰很多,尤其是在按钮有圆角的时候。
跨浏览器差异值得知道
虽然规范是统一的,但各家浏览器对「鼠标点击后程序化 .focus() 是否显示」的处理略有不同:
- Chrome:鼠标点击后调用
.focus()不显示 - Firefox:鼠标点击后调用
.focus()不显示 - Safari:鼠标点击后调用
.focus()显示
设计时不要依赖「点击后 focus ring 不出现」这个行为,只依赖 outline: none on :focus:not(:focus-visible) 这个安全兜底模式就好。
三步下一步
- 全局搜一下
outline: none,看看你的项目里有没有在:focus上直接删环而没有配:focus-visible替代的,有的话就补上 - 输入框例外处理:
<input>和<textarea>应该始终显示焦点环,不要用:focus:not(:focus-visible)把它关掉 - 实测:拔掉鼠标,只用 Tab 键走一遍你页面的主要流程,看看焦点指示器是否清晰可见且不被遮挡
:focus-visible 不是什么新特性,Chrome 86+/Firefox 85+/Safari 15.4+ 都稳定支持了,全球覆盖率 97%+。现在还把它当 bug 或者还留在 outline: none 不放的,是该更新一下认知了。
评论区
登录后可评论。