:focus-visible单独使用无效,因仅在键盘导航时触发;正确做法是先设:focus提供基础反馈,再用:focus:not(:focus-visible)隐藏鼠标触发的轮廓,并确保可访问性。

为什么:focus-visible单独写没用
浏览器只在明确键盘导航(Tab/Shift+Tab/Enter/Space)时触发 :focus-visible,鼠标点击、触摸、el.focus() 默认不匹配。所以只写 button:focus-visible { outline: none; } 完全无效——蓝边框照旧出现,你还以为是兼容性问题。
真正起作用的是组合:先有 :focus 提供基础反馈,再用 :focus:not(:focus-visible) 主动隐藏鼠标触发的轮廓。顺序不能颠倒,否则后写的 :focus 会覆盖前面的 :focus-visible。
-
:focus-visible不是开关,是浏览器基于交互历史动态判断的结果 - 首次页面交互是鼠标点击,后续一段时间内 Safari 和部分 Chromium 可能降级为全走
:focus - 自定义组件(如
div[role="button"])必须带tabindex="0",否则:focus-visible根本不生效
怎么写才不会破坏可访问性
直接 * { outline: none; } 或 button:focus { outline: 0; } 是危险操作。键盘用户 Tab 到按钮后屏幕毫无变化,等于删掉了他们的导航地图。
正确做法是分层兜底:
立即学习“前端免费学习笔记(深入)”;
- 第一层:
button:focus { outline: 2px solid #999; outline-offset: 2px; }—— 提供最低限度、高对比、带偏移的视觉反馈 - 第二层:
button:focus-visible { outline-color: #0066cc; }—— 键盘用户看到更醒目的颜色 - 第三层(可选):
@supports (selector(:focus-visible))包裹增强样式,避免老浏览器报错
注意:outline 颜色与背景对比度必须 ≥ 4.5:1,可用 DevTools 的无障碍检查器验证;outline-offset 要 ≥ 2px,否则圆角按钮边缘会粘连。
移动端和 Safari 的实际表现差异
Safari 15.4+ 支持 :focus-visible,但对触控板点按或首次点击仍可能短暂触发,不能当成“绝对不响应鼠标”的保险栓。
Chrome 86+、Firefox、Edge 表现稳定,但以下情况仍会失效:
- 用了
autofocus—— 自动聚焦不算键盘导航,:focus-visible不匹配 - JS 调用
el.focus()未传{ focusVisible: true }(仅 Chromium 支持) - 元素被
display: none或disabled拦截,即使有tabindex="0"也无效
建议在真实设备上测试 Tab 导航流程,别只靠桌面模拟。
React/Vue 等框架里容易漏掉的关键点
封装过的 Input 或 Button 组件,常因事件代理或 focus 透传不完整,导致原生 :focus-visible 失效。
必须确认:
- 底层 DOM 元素(如
<input>)是否真正接收了焦点,而不是父容器抢了状态 - 组件是否透传了
tabIndex属性,而非仅靠 JS 控制 focus - 某些 UI 库(如 Ant Design)默认禁用
:focus-visible,需手动开启或重写 CSS
最稳妥的方式:在组件根元素上显式加 tabindex="0" 并监听 onFocus,再配合 CSS 选择器定位到真实聚焦节点。
真正难的不是写对那几行 CSS,而是判断当前交互到底是键盘驱动还是鼠标驱动——浏览器不告诉你,只能靠行为模式反推。:focus-visible 的价值不在“省事”,而在把控制权交还给用户意图。


















