:focus-visible 在 Chrome 86 以下不支持,仅用 :focus 会破坏可访问性;js-focus-visible Polyfill 是最稳妥方案,兼容 IE11+、Chrome 49+,需配合类选择器和降级规则使用。

:focus-visible 在 Chrome 86 以下版本(比如企业环境常见的 Chrome 79–85)压根不识别,CSS 规则会被浏览器直接忽略——这不是写法问题,是语法层面不支持。
为什么不能只靠 :focus fallback?
直接用 :focus 替代,会导致鼠标点击按钮/链接时也触发焦点样式,破坏“仅键盘用户需要视觉提示”的可访问性原则。用户点一下就弹个蓝框,体验突兀,还可能干扰设计动效。
更关键的是,:focus 和 :focus-visible 的语义完全不同::focus 表示“当前获得焦点”,:focus-visible 表示“当前获得焦点且由键盘触发”。两者不能混用或简单降级。
用 js-focus-visible Polyfill 是最稳妥的方案
这个轻量脚本(约 2KB gzip)会检测首次键盘操作(如 Tab、Shift+Tab),并在 <html> 上添加 js-focus-visible 类,同时为键盘聚焦的元素加 focus-visible 类。它不依赖 MutationObserver,兼容 IE11+,Chrome 49 起都能跑。
立即学习“前端免费学习笔记(深入)”;
- 引入方式:在
<head>或<body>底部加<script src="https://unpkg.com/js-focus-visible@5.0.0/dist/js-focus-visible.min.js"></script> - CSS 写法必须改成类选择器,例如:
button:focus-visible, button.js-focus-visible .focus-visible { outline: 2px solid #007aff; } - 别漏掉降级规则:
button:focus:not(.js-focus-visible .focus-visible) { outline: none; },否则旧版 Chrome 会回退到默认 outline - 如果项目已用 Webpack/Vite,建议
import 'js-focus-visible'并配sideEffects: false,避免被 tree-shaking 误删
手动模拟 :focus-visible 的边界风险
有人试图用 keydown 监听 + element.focus() 手动打标,但容易踩坑:
- 监听
keydown事件后,需过滤非导航键(如Enter、Space、方向键等),否则输入文字也会触发focus-visible类 -
focus事件本身不携带触发源信息,无法区分是鼠标 click 还是 keyboard focus,必须结合mousedown/keydown时序判断 - 页面有 iframe 或 Shadow DOM 时,事件捕获链断裂,手动逻辑大概率失效
- 移动端 Safari 在非真实用户交互(如 JS 调用
.focus())下不触发focus事件,Polyfill 内部已处理该 case,手写很难覆盖全
真正难的不是加一行 script,而是确保所有可聚焦元素(包括自定义 tabindex 的 div、封装过的 React Button)都被 Polyfill 正确识别——它默认只处理原生可聚焦元素,对 contenteditable 或 role="button" 需手动调用 applyFocusVisiblePolyfill(document) 并传入根节点。


















