:focus-visible 必须与 :focus 配合使用且 :focus 声明在前,否则键盘用户无焦点反馈、鼠标用户无样式变化;仅在真实键盘导航时激活,旧版 Safari 会忽略该规则,需兜底样式保障兼容性。

直接说结论:只写 :focus-visible 样式,键盘用户能看见焦点,鼠标用户会彻底没反馈——这不是 bug,是设计行为。必须搭配 :focus 一起用,且 :focus 必须声明在前。
为什么 :focus-visible 单独写没反应
浏览器只在真实键盘导航(Tab、Shift+Tab、Enter、Space)时激活它;鼠标点击、触摸、甚至 JS 调用 element.focus() 都不会触发。你看到的“蓝框”,其实是 :focus 在起作用。
- 写了
button:focus-visible { outline: none; }却发现点击后蓝框还在 → 因为它根本没匹配上 - Safari 15.3 及更早版本会直接忽略整条
:focus-visible规则,没:focus兜底就等于全失效 - 全局写了
*:focus { outline: none; }→ 后续所有:focus-visible的outline都被干掉
正确写法:顺序、样式、outline-offset 缺一不可
必须按这个结构写,否则视觉和兼容性都会出问题:
-
button:focus在前:提供轻量兜底,比如outline: 1px dotted #999;或box-shadow: 0 0 0 2px rgba(0,118,255,0.2); -
button:focus-visible在后:强化键盘路径,必须带outline-offset: 2px;,避免圆角按钮轮廓贴边或与 border 重叠 - 绝对不要把
:focus-visible写在:focus前面,否则旧浏览器里焦点会彻底不可见
哪些元素默认不触发 :focus-visible?怎么补救
不是加了 tabindex="0" 就自动支持。浏览器根据语义和交互意图判断是否启用该伪类:
立即学习“前端免费学习笔记(深入)”;
-
<div role="button">类组件:必须监听keydown,显式响应 Enter 和 Space,否则 Chromium 可能判定为“非交互意图”,跳过伪类 -
contenteditable容器:自身不触发:focus-visible,需手动加tabindex="0",并在focusin中用 JS 切换.focus-visible类模拟 -
<select>用了appearance: none:部分浏览器丢弃原生焦点样式,得手动补select:focus-visible { outline: ... } -
aria-disabled="true"或disabled状态:即使有tabindex="0",也不会匹配 —— 这是正确行为,别强行绕过
iOS 上的蓝色遮罩不是 :focus-visible 能管的
iPhone 点按钮时看到的半透明蓝 overlay,是 WebKit 的 -webkit-tap-highlight-color 行为,和焦点逻辑完全无关。
- 要关掉它,得单独加:
a, button, [onclick] { -webkit-tap-highlight-color: transparent; } - 这个设置不影响
:focus或:focus-visible的任何行为,只是移除瞬时点击高亮 - 页面加载后 JS 主动调用
element.focus()默认不算键盘导航,:focus-visible不会匹配;如需强制触发,得用element.focus({ focusVisible: true })(仅 Chrome 111+ 支持)
最容易被忽略的是:样式写了但没生效,往往不是语法错,而是 :focus 没写、顺序反了、或者被全局 outline: none 干掉了底层能力。调试时别反复切键盘,直接在 DevTools 里手动勾选元素的 :focus-visible 状态看样式是否生效——这是最快验证方式。


















