:focus-within需父容器加tabindex="0"才能生效,否则无法聚焦;它在子元素获焦时持续匹配,但旧版safari不支持;:hover与:focus-within应分开声明且:focus-within在后;移动端软键盘可能导致焦点丢失,此时需js接管状态控制。

直接用 :focus-within 替换 :focus,但必须给父容器加 tabindex="0",否则它压根不会匹配。
为什么:focus会让子链接点不了
典型写法是 .menu:focus .submenu { display: block; },靠点击带 tabindex="0" 的 <div class="menu"> 触发聚焦。但用户鼠标移向子链接的瞬间,焦点就从 <code>.menu 跳到 <a></a> 上,:focus 立刻失效,.submenu 被隐藏——链接还没触发跳转,菜单先消失了。
这不是 HTML 结构或 CSS 优先级问题,是 :focus 本身只认“当前唯一聚焦元素”,不具备穿透性。
:focus-within 必须配合 tabindex="0"
:focus-within 的规则是:只要该元素自身或其任意后代获得焦点,就持续匹配。但它不会自动生效——父容器得能“被聚焦”才行。
- 必须给菜单容器(比如
<li class="nav-item">或<div class="menu">)加上 <code>tabindex="0" - 不加的话,键盘 Tab 永远进不去,
:focus-within就像没写一样 - 移动端 Safari(iOS 15.4+ / macOS Monterey+)才稳定支持;旧版本会静默忽略整条规则
:hover 和 :focus-within 共存时怎么写才不打架
两个伪类要同时支持鼠标悬停和键盘导航,但不能写成合并选择器,否则容易出现“鼠标移开、键盘还在,菜单却消失了”的情况。
正确写法是分开声明,且让 :focus-within 在后,确保它能覆盖 :hover 的隐藏逻辑:
.nav-item:hover .submenu { opacity: 1; visibility: visible; }
.nav-item:focus-within .submenu { opacity: 1; visibility: visible; }
注意点:
- 别用
.nav-item:hover .submenu, .nav-item:focus-within .submenu { ... }合并写法,浏览器解析行为不一致 -
:focus-within生效的前提是子菜单里真有可聚焦元素(比如<a href></a>、<button></button>),否则焦点进不去,状态不维持 - 用户按
Shift+Tab移出整个容器时,:focus-within仍会退出——这是正常行为,不是 bug
移动端别硬扛:focus-within动画
在 iOS Safari 或部分 Android 浏览器上,软键盘弹出会引发焦点短暂丢失,:focus-within 可能闪退或根本不起作用。此时纯 CSS 方案不可靠。
真机环境建议改用 JS 显式控制状态:
- 监听
focusin(比focus更稳,能捕获冒泡焦点)加is-expanded类 - 监听
focusout后加setTimeout(..., 150)延迟移除类,避开清空按钮等竞态 - CSS 动画改用
max-width+transform,别碰width或height,防止布局重排导致焦点丢失
纯 CSS 的 :focus-within 是轻量解法,但它的“轻量”是以放弃对焦点生命周期的完全控制为代价的——一旦涉及软键盘、快速切换、防抖收起等真实交互细节,JS 就绕不开。











