:focus-visible在safari 15.3及更早版本完全不生效,浏览器直接忽略整条规则;需用:focus兜底并确保其有视觉反馈,且:focus必须在:focus-visible之前声明。

为什么:focus-visible在Safari 15.3及更早版本里完全不生效
浏览器遇到不认识的伪类会整条CSS规则忽略,:focus-visible在Safari 15.3及更早版本中属于“不认识”的范畴。它不会降级为:focus,也不会报错,而是直接跳过整行声明——如果你只写了button:focus-visible { outline: 2px solid blue; },那这些用户连基础焦点反馈都没有。
更隐蔽的是:Safari 15.0–15.3即使开启辅助功能,:focus-visible行为也不稳定;iOS软键盘弹出后,<textarea></textarea>常无法触发该伪类,但:focus仍正常。
- 别用
@supports selector(:focus-visible)包裹整个块——Safari 15.3会因语法解析失败而丢弃整个@supports块,兜底样式也拿不到 - 全局重置
*:focus { outline: none; }会让所有支持/不支持的浏览器都失去焦点提示 - Chrome 111+开始支持
element.focus({ focusVisible: true }),但Firefox和Safari至今不支持该选项
最稳妥的写法::focus在前,:focus-visible在后,且:focus必须有视觉输出
层叠顺序就是降级逻辑。浏览器不支持:focus-visible时,它整条规则被跳过,但前面的:focus仍生效——前提是它写了,且没被其他规则覆盖。
错误示范:button:focus { outline: none; } button:focus-visible { outline: 2px solid #007aff; } → 旧浏览器里按钮点击后彻底“隐身”
正确写法示例:
button:focus {
outline: 1px dotted #999;
outline-offset: 2px;
}
button:focus-visible {
outline: 2px solid #007aff;
outline-offset: 2px;
}
-
:focus提供轻量、不突兀的基础反馈(虚线框比实线更友好) -
:focus-visible用更醒目的样式覆盖,注意加outline-offset防与相邻元素粘连 - 不要给
:focus设outline: none,除非你同时为:focus-visible提供了明确替代(如box-shadow)
自定义组件(如)为何不触发:focus-visible
浏览器不是看tabindex="0"就放行,而是结合语义、交互意图综合判断。一个纯<div role="button">即使可聚焦,若没监听<code>keydown并响应Enter和Space,Chromium可能判定为“非交互意图”,拒绝激活:focus-visible。
- 补救方式一:改用原生
<button type="button"></button>,语义清晰、开箱即用
- 补救方式二:保留
<div role="button">,但必须绑定<code>keydown事件,显式处理Enter/Space,否则伪类永不匹配
- 补救方式三:对
contenteditable容器,它自身不触发:focus-visible,需手动加tabindex="0",并在focusin中用JS切换.focus-visible类模拟
- 禁用态(
aria-disabled="true"或disabled)下,即使有tabindex="0",:focus-visible也不会匹配——这是正确行为,别强行绕过
polyfill 是当前最省心的兼容方案
focus-visible polyfill 在现代浏览器中原生支持时自动静默退出,仅在需要时注入JS逻辑模拟行为。它不依赖@supports,不破坏现有CSS结构,也不要求你重写选择器。
引入方式简单:
<script src="https://unpkg.com/focus-visible@5.2.0/dist/focus-visible.min.js"></script>
- 它会自动为键盘导航的聚焦添加
.focus-visible类,你可以用button.focus-visible写样式,和原生伪类行为一致
- 无需修改现有
:focus规则,polyfill只接管增强层
- 比手写
:focus:not(:focus-visible)更可靠——后者在首次交互为鼠标时可能失效,且移动端兼容性更差
真正容易被忽略的点是:页面加载后JS调用element.focus()默认不算键盘导航,:focus-visible不匹配;polyfill能统一处理这类场景,而原生API目前只有Chromium支持{ focusVisible: true }选项。
浏览器不是看tabindex="0"就放行,而是结合语义、交互意图综合判断。一个纯<div role="button">即使可聚焦,若没监听<code>keydown并响应Enter和Space,Chromium可能判定为“非交互意图”,拒绝激活:focus-visible。
- 补救方式一:改用原生
<button type="button"></button>,语义清晰、开箱即用 - 补救方式二:保留
<div role="button">,但必须绑定<code>keydown事件,显式处理Enter/Space,否则伪类永不匹配 - 补救方式三:对
contenteditable容器,它自身不触发:focus-visible,需手动加tabindex="0",并在focusin中用JS切换.focus-visible类模拟 - 禁用态(
aria-disabled="true"或disabled)下,即使有tabindex="0",:focus-visible也不会匹配——这是正确行为,别强行绕过
polyfill 是当前最省心的兼容方案
focus-visible polyfill 在现代浏览器中原生支持时自动静默退出,仅在需要时注入JS逻辑模拟行为。它不依赖@supports,不破坏现有CSS结构,也不要求你重写选择器。
引入方式简单:
<script src="https://unpkg.com/focus-visible@5.2.0/dist/focus-visible.min.js"></script>
- 它会自动为键盘导航的聚焦添加
.focus-visible类,你可以用button.focus-visible写样式,和原生伪类行为一致 - 无需修改现有
:focus规则,polyfill只接管增强层 - 比手写
:focus:not(:focus-visible)更可靠——后者在首次交互为鼠标时可能失效,且移动端兼容性更差
真正容易被忽略的点是:页面加载后JS调用element.focus()默认不算键盘导航,:focus-visible不匹配;polyfill能统一处理这类场景,而原生API目前只有Chromium支持{ focusVisible: true }选项。











