:focus-within更适合搜索建议列表,因为它能监听父容器内任意子元素(如设tabindex的)获得焦点,从而触发列表显隐和高亮;而:focus仅作用于输入框本身。

为什么:focus-within比:focus更适合搜索建议列表
单独用 :focus 只能作用在获得焦点的输入框本身,无法让下拉列表里的高亮项同步响应;而 :focus-within 会监听整个父容器内任意子元素获得焦点——只要用户用键盘上下键移动到某个 <li> 上(并给它 tabindex="0"),父容器就仍处于“被聚焦状态”,你就能靠它触发列表整体的显隐、边框、阴影等视觉反馈。
常见错误是直接给 <ul></ul> 加 :focus-within 却忘了子项没可聚焦能力,结果键盘操作时样式完全不更新。
- 必须给每个建议项(如
<li>)设置tabindex="0"或tabindex="-1"(后者需配合 JS 手动.focus()) -
<input>和建议列表需同属一个父容器(如<div class="search-wrapper">),否则 <code>:focus-within不生效 - Chrome/Firefox/Edge 100+ 均支持,但 Safari 15.4+ 才稳定支持,旧版需降级 fallback
如何用:focus-within控制列表显隐与选中态
核心逻辑是:输入框获得焦点 + 有建议数据 → 列表显示;任一建议项获得焦点 → 该项高亮,其他项取消高亮。不需要 JS 控制显隐,纯 CSS 就能联动。
示例结构:
<div class="search-wrapper">
<input type="text" class="search-input" autocomplete="off"><ul class="suggestions-list">
<li tabindex="-1">React</li>
<li tabindex="-1">Redux</li>
<li tabindex="-1">Remix</li>
</ul>
</div>
CSS 关键片段:
.search-wrapper .suggestions-list {
display: none;
}
.search-wrapper:focus-within .suggestions-list {
display: block;
}
.suggestions-list li:focus {
background-color: #007aff;
color: white;
}
/* 防止点击后 input 失焦导致列表消失 */
.search-wrapper:hover .suggestions-list,
.search-wrapper:focus-within .suggestions-list {
display: block;
}
-
tabindex="-1"是推荐写法:它让元素可通过 JS 聚焦,又不会被 Tab 键自然遍历打乱顺序 - 加
:hover是必要兜底——鼠标悬停时也应保持列表可见,否则交互割裂 - 不要对
<ul></ul>本身设tabindex,否则键盘焦点会落在空白区域,无法精准选中某条建议
键盘导航时的 focus 状态管理要点
仅靠 CSS 无法完成上下键移动逻辑,JS 仍需接管键盘事件,但 CSS 的职责是「忠实反映当前焦点位置」。关键在于确保每次 .focus() 调用后,浏览器真实触发了 focus 事件,且元素已渲染可聚焦。
- 首次打开列表后,应立即对第一个
<li>调用.focus(),否则键盘按 ↓ 无响应 - 用
ArrowDown/ArrowUp切换时,记得用preventDefault()阻止输入框光标跳动 - 失焦处理要谨慎:
blur事件可能来自点击外部、Tab 离开、或 Esc 按下,需区分场景决定是否隐藏列表 - Safari 中
tabindex="-1"元素调用.focus()后,有时不触发:focus样式,可临时改用tabindex="0"测试是否为渲染时机问题
兼容性差时的最小可用 fallback 方案
如果项目必须支持 Safari 15.3 或某些安卓 WebView,:focus-within 不可用,就得退回到 JS 驱动显隐 + class 切换。但 CSS 层仍可保留 :focus 样式,避免重复劳动。
最简 fallback:
.search-wrapper.js-focus-within .suggestions-list {
display: block;
}
.suggestions-list li.is-selected {
background-color: #007aff;
color: white;
}
- JS 在 input
focus时给.search-wrapper加js-focus-within类 - 键盘移动时,用 JS 切换
is-selected类,而非依赖:focus - 这样 CSS 逻辑不变,只是触发方式从伪类变成 class,维护成本低
真正容易被忽略的是:移动端软键盘弹出时,focus 行为不稳定,tabindex="-1" 元素在 iOS 上可能无法被 .focus() 成功激活,得结合 setTimeout 延迟或监听 keyboardDidShow 类似事件再聚焦。











