:focus-visible 必须与 :focus 配合使用且 :focus 在前,否则鼠标用户无焦点反馈、旧版 safari 无兜底样式;仅键盘导航触发 :focus-visible,js focus() 默认不匹配。

只写 :focus-visible 样式,键盘用户能看见焦点,鼠标用户会彻底失去反馈——这不是 bug,是设计行为。必须搭配 :focus 一起用,且顺序不能错。
为什么单独用 :focus-visible 会导致鼠标点击“失明”
浏览器只在 Tab、Shift+Tab、方向键、Enter、Space 这类键盘导航行为下触发 :focus-visible;鼠标点击、触摸 tap、甚至 JS 调用 element.focus() 都不会匹配它。所以如果只写 button:focus-visible { outline: 2px solid #007aff; },鼠标点完按钮,页面毫无视觉变化,用户无法确认是否生效。
更关键的是兼容性:Safari 15.3 及更早版本会直接忽略整条 :focus-visible 规则;如果没有前置的 :focus 声明,这部分用户就完全看不到任何焦点样式。
- 别指望
:focus-visible替代:focus,它们解决不同问题 -
:focus是所有浏览器都支持的基础锚点,必须提供轻量但可见的反馈(哪怕只是outline: 1px dotted #999) -
:focus-visible是增强层,仅用于键盘路径的差异化强化
button:focus 和 button:focus-visible 的声明顺序与取值逻辑
顺序错了,整个可访问性链路就断了。浏览器不支持 :focus-visible 时,它整条规则被跳过;此时必须靠 :focus 接住所有聚焦行为。
- ✅ 正确写法:
button:focus { outline: 1px dotted #999; outline-offset: 2px; }在前,button:focus-visible { outline: 2px solid #007aff; outline-offset: 2px; }在后 - ❌ 错误写法:
:focus-visible写在:focus前面,或两者都加!important导致层叠混乱 - ⚠️ 危险操作:
*:focus { outline: none; }——这会把:focus-visible的样式也一并干掉
哪些元素默认不触发 :focus-visible?怎么补救
不是加了 tabindex="0" 就自动支持。浏览器根据语义和交互意图判断是否启用该伪类,尤其对自定义组件非常敏感。
- ✅ 默认支持:
<button></button>、<input>、<textarea></textarea>、<select></select> - ❌ 默认不触发:
<div role="button">(缺 <code>keydown监听)、<div contenteditable>(自身不触发)、禁用态(<code>disabled或aria-disabled="true") - 补救方法:
<div role="button" tabindex="0"> 必须监听 <code>keydown,并在event.key === 'Enter' || event.key === ' '时触发对应操作 - 自定义
contenteditable容器需手动加tabindex="0",并在focusin中通过 JS 切换.focus-visible类模拟 - Safari 15.3 及更早版本不识别
:focus-visible,必须靠:focus兜底,不能只靠@supports包裹美化规则 - JS 调用
element.focus()默认不算键盘导航,:focus-visible不匹配;Chrome 111+ 支持element.focus({ focusVisible: true })强制触发 -
outline-offset至少设为2px,否则圆角按钮或紧凑工具栏里容易和相邻元素粘连 - 移动端触屏点击后
:focus可能短暂触发又消失,别强行统一样式,应优先分离逻辑
调试和兼容性最容易被忽略的点
真正难的不是写几行 CSS,而是让每个可聚焦元素在各种场景下都有稳定、一致、不依赖颜色的反馈。最常被绕过的细节:











