:focus-within 是 CSS 伪类,当元素自身或其可聚焦后代获得焦点时匹配父容器,解决纯 CSS 响应子元素焦点状态的问题,如表单卡片高亮,无需 JS。

:focus-within 是什么,它能解决什么问题
:focus-within 是一个 CSS 伪类,当元素自身或其任意后代获得焦点(比如用户点击输入框、按 Tab 键进入)时,该伪类匹配父容器。它不是“监听子元素”,而是浏览器原生支持的焦点传播检测机制——只要子树里有 focusable 元素处于聚焦状态,父级就能响应。
典型场景:表单卡片悬停时看不出交互,但用户 tab 进入内部 <input> 后,整个卡片需要高亮提示当前操作区域。不用 JS 绑事件、不用管理焦点状态,纯 CSS 就能完成。
哪些子元素触发 :focus-within 有效
只有可聚焦(focusable)的元素才能激活 :focus-within。常见有效元素包括:
-
<input>、<textarea>、<select>、<button> - 带
tabindex属性的元素(如tabindex="0"或tabindex="-1") -
<a>标签(有href时默认可聚焦)
无效情况举例:<div>、<p>、<span> 默认不可聚焦,即使加了 tabindex="-1" 但没显式调用 .focus(),也不会触发 :focus-within —— 浏览器只认“当前是否真的有焦点”,不是“能否被聚焦”。
立即学习“前端免费学习笔记(深入)”;
写法和容易踩的坑
直接在父容器上写 :focus-within 即可,但要注意层级和选择器精度:
- 父元素必须是实际包裹 focusable 子元素的 DOM 节点,不能跨级跳选(比如给
.card写:focus-within,但<input>在.card > .form > .field里,依然生效) - 不要和
:hover混用在同一规则里写成:hover:focus-within—— 这表示“同时满足 hover 和 focus-within”,而通常你需要的是“任一满足”,应分开写或用逗号分隔 - 旧版 Safari(≤15.4)不支持
:focus-within,iOS 15.4 及更早也缺失;如需兼容,得 fallback 到 JS 监听focusin/focusout事件并切换 class
示例:
/* 正确:父容器在子 input 聚焦时变背景 */
.card:focus-within {
background-color: #f0f8ff;
}
<p>/<em> 错误:这样写不会生效,因为 div 默认不可聚焦 </em>/
.card div:focus-within { ... }</p><p>/<em> 错误::hover:focus-within 是交集,不是并集 </em>/
.card:hover:focus-within { ... } /<em> 应改为 .card:hover, .card:focus-within { ... } </em>/为什么有时 :focus-within 不生效
最常见原因不是语法错,而是焦点被“吞掉”或“没真正落在目标元素上”:
- 子元素设置了
outline: none且没提供其他视觉反馈,让人误以为没聚焦(其实已聚焦,:focus-within已生效) - 使用了
pointer-events: none的父层遮盖了子元素,导致点击无法触发 focus(注意:focus 可通过键盘触发,但鼠标点击失效) - 子元素被
display: none或visibility: hidden隐藏 ——:focus-within不会匹配隐藏元素的父级(visibility: hidden下仍可聚焦,但display: none完全移出渲染树,无法触发) - React/Vue 等框架中,动态渲染的 input 如果初始未挂载,后续插入后未重置 tabindex 或未正确插入 tab 顺序流,也可能导致首次 tab 进不去
调试建议:打开开发者工具,选中子 <input>,看 Elements 面板里该元素是否带 :focus 状态;再看父级是否同步出现 :focus-within 样式计算结果。
真正麻烦的不是怎么写,而是确认焦点路径是否干净——有没有 invisible overlay、有没有 tabIndex 乱序、有没有被 JS 中途 preventDefault() 掉 focus 事件。


















