:focus-within 可替代 JS 实现输入框补全列表显隐,前提是 input 和下拉 ul 同属一个容器;需用 opacity/visibility 控制显隐、同步更新 aria-expanded、处理 autofill 干扰及键盘导航,并为旧浏览器提供 JS 降级。

能不用 JS 就别用——只要把 和补全列表包进同一个容器,:focus-within 就能自动响应所有焦点进入行为,包括 Tab 进入、点击输入框、键盘选中下拉项。
必须用容器包裹 input 和下拉列表
这是生效的前提。浏览器只在「父容器自身或其任意后代获得焦点」时触发 :focus-within,所以结构必须是:
<div class="search-container"> <input type="text" aria-controls="suggestions"><ul id="suggestions" class="suggestions"></ul> </div>
常见错误:把 放到 底层或用 position: fixed 脱离文档流,导致它不再是容器的后代,:focus-within 失效。
正确做法:
- 确保
是.search-container的直接或间接子元素 - 给容器加
role="combobox"和aria-expanded="false",为可访问性打基础 - 不要给容器设
tabindex="-1"或tabindex="0"——除非你真需要它本身可聚焦(一般不需要)
隐藏/显示不能用 display: none
用 display: none 会让下拉列表完全退出 tab 顺序和焦点树,键盘用户按方向键时无法进入列表项,:focus-within 在焦点移入列表后还会意外失效。
应该用组合方式控制显隐:
- 初始状态:
opacity: 0; visibility: hidden; pointer-events: none; -
.search-container:focus-within .suggestions中设为:opacity: 1; visibility: visible; pointer-events: auto; - 加
transition: opacity 0.2s ease实现淡入,避免突兀
注意:不要只靠 CSS 切换显隐,aria-expanded 必须同步用 JS 更新,否则屏幕阅读器读不出状态变化。
处理 autofill 和滚动容器干扰
Chrome 的原生 autofill 会强行弹出下拉框,覆盖你的自定义补全列表,造成错位或闪烁。
缓解方案:
- 给
加autocomplete="off"不可靠;改用autocomplete="new-password"或带时间戳的值,如autocomplete="search-1730560149" - 下拉列表超过 5 条时需加
max-height: 200px; overflow-y: auto;,但滚动容器本身不可聚焦,会导致焦点离开列表时容器失焦 - 解决办法:给
的每个加tabindex="0",确保键盘能进入并停留其中
滚动条本身不是可聚焦元素,所以别指望用户能用 Tab 键聚焦到滚动条上——这根本不是设计目标。
兼容性与降级要点
:focus-within 在 IE 中完全不支持,Edge 79+ 才开始支持,Safari 15.4+ 稳定可用。如果你的项目仍需支持旧版 Edge 或 IE,JS fallback 不可省略。
降级逻辑要简洁:
- 检测
CSS.supports('selector(:focus-within)'),不支持时才挂载 focus/blur 监听器 - 监听
input的focus和blur,配合setTimeout延迟判断是否点在下拉区外(避免 blur 误触发收起) - 别忘了键盘导航:监听
ArrowDown/Escape等键,手动控制展开/收起和焦点转移
最易被忽略的是:aria-controls 的值必须严格匹配下拉列表的 id,拼写差一个字符,辅助技术就认不出来。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











