:focus-visible 必须与 :focus 组合使用才有效,单独写无效果;正确写法是 button:focus:not(:focus-visible) { outline: none; },且需紧接在 button:focus 之后。

为什么单独写 :focus-visible 没效果
浏览器根本不会在鼠标点击时触发 :focus-visible——它只响应键盘导航(Tab/Shift+Tab/Enter/Space),所以 button:focus-visible { outline: none; } 这种写法等于没写,蓝边框照常出现。更麻烦的是,你可能误以为是兼容性问题,其实是逻辑没搭对。
:focus-visible 不是开关,而是浏览器根据交互历史动态判断的伪类:鼠标点击、触摸、el.focus() 都不满足触发条件。它必须依赖 :focus 作为基础状态才能叠加生效。
- 全局写了
*:focus { outline: none; }?那:focus-visible就彻底失效——它需要先有:focus才能判断是否“可见” - 自定义组件(如
div[role="button"])没加tabindex="0"?:focus-visible根本不匹配 - Safari 对首次点击或触控板点按仍可能短暂触发,不能当成“绝对不触发”来设计
:focus:not(:focus-visible) 才是控制鼠标焦点的关键
真正让鼠标点击不显示轮廓的,是这个组合伪类。它匹配“获得焦点但不是键盘触发”的所有情况,适合统一清除 outline。
写法必须是:button:focus:not(:focus-visible) { outline: none; },且顺序必须紧接在 button:focus 之后。
立即学习“前端免费学习笔记(深入)”;
- 不能反过来写
:focus-visible在前——CSS 层叠优先级相同,后声明者胜出,:focus会覆盖掉它 - 别用
input:focus { outline: none; }开头,否则鼠标和键盘都看不到焦点,直接废掉可访问性 - 推荐渐进增强写法:
input:focus { outline: 2px solid #999; }+@supports (selector(:focus-visible)) { input:focus-visible { outline: 2px solid #0066cc; outline-offset: 2px; } }
哪些场景下 :focus-visible 会静默失效
它依赖 UA 启发式判断,并不稳定。真机测试比 DevTools 模拟更重要。
- 页面首次交互是鼠标(比如加载完立刻点按钮),后续一段时间内浏览器可能“降级”为全用
:focus -
autofocus触发的聚焦不算键盘导航,:focus-visible不匹配 - JS 调用
el.focus()但没传{ focusVisible: true }(仅 Chromium 支持,且需配合HTMLElement.focusOptions) - React/Vue 封装的输入框若内部调用
.focus()却没透传 focus 选项,:focus-visible很可能不触发 - Shadow DOM 中样式作用域受限,外层
:focus-visible无法穿透,必须在组件内部显式定义
移动端和深色模式下的实际坑点
iOS Safari 对 input[type="number"] 的焦点行为延迟明显,建议统一用 type="text" + 输入限制;深色背景下,仅靠颜色变化的焦点样式极易不可见。
- 务必验证对比度 ≥ 4.5:1(可用 DevTools 的无障碍检查器)
- 别只改
border-color,深色背景上蓝色边框可能完全融进去 - 用
outline-offset防止和圆角粘连,尤其对图标按钮或小尺寸控件 - 避免在重置 CSS 中写
*:focus { outline: none; },它会直接废掉整个链条
真正难的不是写出几行 :focus-visible,而是坚持让每个可聚焦元素都有明确、一致、不依赖颜色的视觉反馈——哪怕用户从不碰键盘,这行代码也早已悄悄撑住了另一个人的浏览路径。


















