bootstrap 5 默认不支持下拉搜索,需手动组合dropdown组件并处理事件冒泡、键盘导航与实时过滤;核心是用dropdown-menu包裹input和列表,通过show.bs.dropdown重置状态,监听input和keydown实现搜索与焦点控制。

Bootstrap 5 默认不支持下拉搜索,得自己加
Bootstrap 官方的 dropdown 组件压根没集成搜索功能,所谓“带搜索的下拉列表”是常见业务需求,但得靠手动组合实现。别指望 bootstrap.bundle.min.js 加个 class 就能出搜索框——它只负责展开/收起和菜单定位,输入逻辑、过滤、键盘导航全得你补。
典型错误是直接在 <div class="dropdown-menu"> 里塞一个 <code><input>,结果点击输入框时下拉菜单立刻关闭(因为 Bootstrap 的 click outside 机制会监听 document 点击并 hide 菜单)。这不是 bug,是设计如此。
- 必须阻止搜索框的点击事件冒泡到 document 的隐藏监听器
- 需要监听
input事件做实时过滤,而不是等回车 - 建议把搜索框放在
dropdown-menu内部顶部,并加border-bottom分隔,否则视觉上容易误判为独立控件
用原生 HTML + JS 实现最轻量方案
不引入 Select2 或 Choices.js 这类第三方库,也能干净解决。核心思路:用 dropdown-menu 包裹 <input> 和 <ul></ul> 列表,JS 控制列表项显隐。
关键点在于事件隔离:input 的 click 和 focus 必须调用 event.stopPropagation(),否则菜单闪退;同时要监听 keydown 处理上下键聚焦选项(不然键盘用户没法用)。
- 搜索框
type="text",加class="form-control form-control-sm"保持样式统一 - 每个选项用
<li class="dropdown-item">,不要用<a></a>(避免默认跳转行为干扰) - 过滤时只比对文本内容(
textContent),忽略 HTML 标签或子元素 - 性能注意:选项超过 200 条时,用
display: none切换比 DOM 增删快得多
<div class="dropdown">
<a class="btn btn-outline-secondary dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown">
选择城市
</a>
<ul class="dropdown-menu">
<li><input type="text" class="form-control form-control-sm m-2" placeholder="搜索..."></li>
<li><a class="dropdown-item" href="#">北京</a></li>
<li><a class="dropdown-item" href="#">上海</a></li>
<li><a class="dropdown-item" href="#">广州</a></li>
</ul>
</div>
用 Bootstrap 5 的事件 API 控制展开逻辑
不能只靠 CSS 和静态 HTML。必须用 show.bs.dropdown 和 hide.bs.dropdown 钩子来初始化搜索逻辑,否则每次点击 toggle 按钮都会重置搜索状态。
常见坑是把初始化代码写在页面加载后就执行一次,结果第二次展开时搜索框没清空、上次过滤结果还留着。正确做法是在 show.bs.dropdown 中重置输入框、显示全部选项;在 hide.bs.dropdown 中可选地保存最后输入值(比如用于下次自动聚焦)。
- 监听
show.bs.dropdown时,调用input.value = ""并移除所有style="display:none" - 给搜索框绑定
input事件,用Array.from(menuItems).forEach(...)遍历dropdown-item元素 - 注意
dropdown-menu可能包含分组标题(dropdown-header),过滤时应跳过它们 - 兼容性:Bootstrap 5.3+ 支持
data-bs-reference自定义定位,但搜索框本身不影响 popper 计算,无需额外配置
键盘操作必须补全,否则不符合可访问性要求
只支持鼠标点选 + 输入过滤,不算真正可用。用户按 ↓ 键应该聚焦第一个匹配项,再按 ↓ 移动到下一个,Enter 触发选择,Esc 关闭菜单——这些不是锦上添花,是 WCAG 2.1 基础要求。
最容易被忽略的是:当搜索框获得焦点时,按 ↓ 应该直接跳到第一个可见选项(而不是卡在输入框里),这需要监听 keydown 并手动调用 focus()。而且得区分“有匹配项”和“无匹配项”两种状态,后者按 ↓ 应该什么也不做。
- 用
input.addEventListener("keydown", e => { if (e.key === "ArrowDown") { ... } })拦截 - 维护一个当前聚焦索引变量,配合
menuItems[i].focus() - 按
Enter时,检查当前聚焦元素是否为dropdown-item,再触发其 click 事件 - 别忘了
Tab键逻辑:默认 Tab 会跳出下拉菜单,需用e.preventDefault()拦截并手动切到下一个可聚焦项











