用:focus-visible替代:focus可精准区分键盘与鼠标焦点,但需确保dom语义正确、焦点可达且outline未被全局清除。

直接结论:用 :focus-visible 替代 :focus 给编辑器内可操作元素加焦点样式,就能让键盘用户看清当前光标在哪、正操作哪个按钮或工具栏项,而鼠标用户完全不被打扰——但前提是编辑器 DOM 结构语义正确、焦点能真正落到目标上,且没被全局 outline: none 误杀。
为什么编辑器里 :focus-visible 常常“不亮”
不是代码写错了,而是编辑器这类复杂组件触发焦点的路径太绕:
- 用户点击工具栏按钮后按空格/回车,浏览器仍判定为“鼠标引发的焦点”,
:focus-visible不匹配 -
contenteditable区域本身不默认触发:focus-visible(哪怕加了tabindex="0"),必须靠 JS 主动管理焦点来源 - 编辑器封装了
div[role="button"]或span[tabindex="0"]的自定义控件,但没监听keydown事件去标记键盘模式,浏览器拒绝激活伪类 - 整个 UI 库用了
* { outline: none },连:focus-visible的基础outline也被干掉了,样式写了等于没写
:focus-visible 在编辑器中该加给谁
别一股脑全选 [tabindex],得按语义分层处理:
- 工具栏按钮(
button、div[role="button"]):必须加,且确保有tabindex="0"和keydown监听(响应Enter/Space) -
contenteditable容器:它自己不触发:focus-visible,但内部光标位置靠::selection或自定义高亮;真正要样式的是它获得焦点时的“容器边框”——需手动加tabindex="0"并监听focusin,再用 JS 切换.focus-visible类 - 下拉菜单、颜色选择器等浮层控件:根节点设
tabindex="-1",首次打开后 JS 调用.focus();内部选项用roving tabindex(仅一个tabindex="0",其余为-1),每个选项都需支持:focus-visible - 禁用状态(
aria-disabled="true")的按钮:即使加了tabindex="0",也不能让它可聚焦——否则键盘用户会停在“不可用”的地方,白等
样式声明顺序和兼容性兜底必须做对
编辑器往往要兼容旧版 Safari(比如 15.3 及更早),CSS 写法稍错就全失效:
- 必须先写
button:focus { outline: none; },再写button:focus-visible { outline: 2px solid #007aff; outline-offset: 2px; }——靠层叠顺序降级,不是靠!important - 如果用 polyfill(如
focus-visible.js),引入后要加@supports not selector(:focus-visible)块,把.focus-visible类样式也写进去,否则老浏览器直接忽略整条规则 -
outline-offset: 2px别省,编辑器工具栏图标密集,紧贴边框的轮廓容易和相邻元素粘连,呼吸感一丢,可读性就垮 - 别只依赖
outline:部分编辑器主题禁用所有outline,改用box-shadow或border作为 fallback,但要注意border会改变布局尺寸,box-shadow更安全
调试时最该盯住的三个点
编辑器里焦点流是动态的,不能只看 CSS 是否生效:
- 在 DevTools 控制台跑
document.activeElement,确认焦点真落到了你写的那个button上,而不是飘到了contenteditable内部某个textNode - 检查目标元素的
tabIndex属性值是否 ≥ 0,且没被pointer-events: none或visibility: hidden拦截 - 用键盘 Tab 几次后,手动在 Elements 面板勾选该元素的
:focus-visible状态——如果样式立刻出现,说明是交互检测逻辑问题;如果连勾选都不生效,大概率是元素不可聚焦或被全局样式覆盖了
编辑器的焦点体验不是加个伪类就完事,它卡在 DOM 语义、JS 焦点管理、CSS 层叠逻辑三者的交界处。最容易被忽略的是:第一次页面交互如果是鼠标,后续键盘导航可能持续不触发 :focus-visible,直到用户按一次 Tab 或 Esc 重置输入模式——这个细节连很多成熟编辑器都没处理好。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











