原生 天然可访问,因其被浏览器视为控件,自动支持tab聚焦、方向键导航、aria语义及键盘事件;而默认无语义,需手动实现焦点流、role="combobox"与aria-expanded成对使用、键盘事件监听(arrow/enter/esc)、移动端touchstart协同及表单值同步。

原生 <select></select> 是唯一开箱即用、键盘可访问的下拉方案;所有自定义实现(<div>+<code><ul></ul>)必须手动补全焦点流、ARIA 语义和键盘事件逻辑,否则对键盘用户和屏幕阅读器完全不可用。
为什么 <select></select> 天然可访问,而 <div> 不行
<p>浏览器把 <code><select></select> 当作“控件”处理:Tab 可进入、Arrow 键切换选项、Enter/Space 展开收起、aria-expanded 和 role="combobox" 都由 UA 自动管理。但 <div> 默认是“容器”,没有语义、不参与聚焦流、不响应方向键——加了 <code>tabindex="0" 也只解决“能进”,不解决“怎么导航”“怎么选中”“怎么播报”。
常见错误现象:
- Tab 进入后按 ArrowDown 没反应,或页面向下滚动
- 屏幕阅读器读作“静态列表”,而非“下拉列表,已折叠”
- 按 Enter 后菜单收起但没选中值,或直接触发了按钮的 click 事件
role="combobox" + aria-expanded 必须成对出现
这两个属性不是可选项,是屏幕阅读器识别下拉行为的最小语义单元。仅设 role="combobox" 而不控制 aria-expanded,JAWS/NVDA 会误判为“只读组合框”;反之,只切 aria-expanded 而不设 role,则根本不会播报“下拉”状态。
实操要点:
- 触发按钮(如
<button></button>)必须同时有role="combobox"、aria-haspopup="listbox"、aria-expanded="false"和aria-controls="dropdown-id" - 下拉容器(
<ul></ul>或<div role="listbox">)ID 必须与 <code>aria-controls值一致,并设role="listbox" - 每个选项用
<li role="option">,并动态同步aria-selected="true",不能只靠 CSS 类名标记选中 - 对
ArrowUp/ArrowDown必须调用e.preventDefault(),否则页面会滚动 - 维护一个
focusedIndex变量,避免依赖 DOM 顺序(比如选项被display: none隐藏时) -
Enter和Space都要处理,且行为一致:选中当前聚焦项 + 收起菜单 + 把焦点还给触发按钮 -
Escape触发后立即收起,并调用triggerButton.focus(),不能只改aria-expanded - 不要监听
click来替代Enter—— 键盘用户不会触发 click,且 screen reader 对按钮的 Space 行为有固定预期 - 在触发按钮上监听
touchstart并立即e.preventDefault(),延迟 100ms 再展开面板,避开合成点击 - 展开后手动调用
options[0].focus(),别等浏览器自动聚焦 - 避免给下拉容器设
tabindex="0"—— 它会让 Tab 穿透进去,且 iOS 下焦点可能卡死在不可见区域 - 收起逻辑优先用
focusout(监听触发按钮)+mousedown(监听 body),而不是单纯靠blur,后者在 Firefox 中根本不触发
键盘事件监听必须覆盖 ArrowUp/ArrowDown/Enter/Escape
仅靠 tabindex 和 ARIA 不足以完成交互闭环。方向键要移动焦点、回车要确认、ESC 要收起——这些都得手写事件逻辑,且不能只监听 keydown 就完事。
关键细节:
移动端 touchstart 与焦点丢失的协同处理
iOS Safari 中,点击 <button></button> 后可能立刻触发 focus,但下拉面板还没渲染完成,导致焦点落在空白处或直接丢失。更糟的是,click 有 300ms 延迟,常与 touchstart 冲突,造成点一次开两次、或点开即关。
实操建议:
最易被忽略的一点:所有键盘操作必须同步更新 aria-selected 和隐藏 <input type="hidden"> 的 value,否则表单提交时拿到的是旧值;而这个同步动作本身,又必须在 Enter/Space 的事件处理器里完成,不能等到 click 或异步回调里再做。











