原生天然可访问,因浏览器内置完整无障碍支持;自定义下拉必须手动实现5个核心点:角色声明、状态同步、键盘导航、焦点管理及aria语义补全,缺一不可。

原生 <select></select> 是唯一能开箱即用、无需额外逻辑就满足无障碍要求的下拉控件;所有自定义实现(div + ul + JS)都必须手动补全键盘导航、状态同步和 ARIA 语义,否则对屏幕阅读器用户基本不可用。
为什么原生 select 天然可访问
浏览器为 <select></select> 内置了完整的辅助技术支持:Tab 进出、方向键切换选项、Enter/Space 展开收起、Esc 关闭并返回焦点、自动朗读“已选择 X”等。这些行为不依赖 JS,也不需要开发者写一行 ARIA。
-
id和<label for="xxx"></label>配对后,屏幕阅读器能准确关联标签与控件 - 每个
<option></option>的文本内容会被自然朗读,value不暴露给用户,只用于提交 -
<optgroup label="xxx"></optgroup>会被朗读为分组标题,且不可选,语义清晰 - 禁用状态(
disabled)会被明确告知,焦点不会落入其中
自定义下拉必须补全的 5 个核心点
一旦放弃 <select></select>,你就接手了整套交互契约。漏掉任意一项,键盘或屏幕阅读器用户就会卡住。
- 用
role="combobox"声明容器角色,aria-haspopup="listbox"和aria-expanded动态控制展开状态 - 触发按钮需有
aria-controls="listbox-id"指向选项列表,且自身不能是<div> —— 必须是 <code><button></button>或带tabindex="0"的可聚焦元素 - 选项列表用
role="listbox",每个选项用role="option",并设aria-selected同步选中态 - 键盘响应必须覆盖:Tab 进出(非内部遍历)、
ArrowDown/ArrowUp切换焦点、Home/End跳首尾、Enter/Space确认、Escape关闭并还原焦点 - 焦点管理要严格:展开时焦点移到第一个选项;关闭后焦点必须回到触发按钮,不能丢失或跳到页面顶部
- 只加
role="listbox"却没给父容器设role="combobox"→ 屏幕阅读器识别为普通列表,不提示“可输入”或“可展开” - 用
<div onclick> 当触发器,没设 <code>tabindex="0"→ 键盘用户根本无法到达该按钮 - 选项用
<li>但没加role="option"→ 辅助技术忽略其可选性,朗读成普通文字 -
aria-expanded="true"但列表 DOM 未渲染,或反之 → 状态与视觉/行为严重脱节,造成混淆 - 关闭菜单后焦点消失或落到
→ 用户必须重新 Tab 寻找下一个控件,体验断裂
常见 ARIA 错误与后果
很多“看起来能用”的自定义下拉,在无障碍测试中会直接失败,原因往往很具体:
最易被忽略的是焦点还原逻辑——它不难写,但常被当成“锦上添花”,而实际是键盘用户能否继续表单操作的生命线。只要没做这一步,整个自定义下拉在无障碍意义上就是残缺的。











