原生 select[multiple] 是质量最高的多选实现,关键在于语义正确、label 显式关联、size 或 CSS 控制可视高度、必填/错误/禁用使用原生属性,自定义组件需完整实现 ARIA 和键盘导航。

select[multiple] 的结构合理性不取决于是否“看起来高级”,而在于它能否被键盘、屏幕阅读器和表单提交机制一致识别——原生多选框只要语义正确,就是质量最高的实现。
label 与 select[multiple] 必须显式关联,不能靠视觉对齐
屏幕阅读器不会读“上面那个文字”,只读 DOM 中 label 节点是否通过 for 属性指向 select 的 id。常见错误包括:
- 写了
label但漏掉for属性,或拼错id(比如id="colors"对应for="color") - 用
div或p模拟 label,即使加了aria-label,也无法触发浏览器的自动聚焦行为(点击 label 时 input 不获焦) - 多个
select共用一个id,导致部分控件完全失联
实操建议:始终用 <label for="colors">选择颜色</label><select id="colors" multiple>;避免嵌套写法 <label>文本<select></select></label>,旧版 NVDA 在某些上下文中会截断播报。
multiple 属性必须配合 size 或 CSS 控制可视行数
原生 select[multiple] 默认渲染为单行输入框(仅显示已选项的逗号分隔列表),用户无法感知“这是个多选框”,也无法直接看到全部选项。这直接破坏可发现性。
立即学习“前端免费学习笔记(深入)”;
- 不设
size且无 CSS 修正时,屏幕阅读器可能只报“组合框,已选 2 项”,却不读选项内容 -
size="3"表示默认展开显示 3 行选项,是 WCAG 推荐的最小可视提示 - 若用 CSS 覆盖高度(如
height: 120px),需同步确保overflow-y: auto和键盘↑/↓可滚动,否则键盘用户卡死在前几项
注意:size 是 HTML 属性,不是 CSS;它影响的是初始展开高度,不影响提交数据格式。
必填、错误、禁用状态必须用原生属性驱动,而非仅样式
仅加红色边框或星号图标,对屏幕阅读器用户等于没提示。关键状态必须由浏览器原生理解:
-
required:必须存在,不可用aria-required="true"替代;屏幕阅读器会明确读出“必填” -
aria-invalid="true"+aria-describedby="error-id":验证失败时设置,并确保id="error-id"的元素在 DOM 中且唯一;不要只改border-color -
disabled:真正禁用交互,移除焦点能力;禁用时不要只加tabindex="-1"+opacity: 0.5,那样键盘仍可 Tab 进去,辅助技术仍可操作
性能提示:频繁切换 aria-invalid 本身开销极小,但若 aria-describedby 指向的元素被 JS 动态创建/销毁,ID 就可能失效——错误信息容器建议提前静态写入 DOM。
复杂场景下,select[multiple] 不是唯一解,但替换前要算清成本
当需求超出原生能力(如搜索过滤、标签化展示、拖拽排序),自定义多选组件确实更灵活。但代价是必须手动补全整套 ARIA:
- 角色需设为
role="listbox"或role="combobox",不能只用div+role="button" - 每个可选项必须是
role="option",并用aria-selected="true"同步状态 - 必须实现完整的键盘导航:
↑/↓切换焦点、Space切换选中、Enter确认、Esc关闭弹层 - 焦点管理必须严格:打开时自动聚焦首项,关闭后返回触发按钮
最容易被忽略的一点:原生 select[multiple] 提交时自动序列化为 name=value1&name=value2,而自定义组件若用 JS 收集值,必须确保后端能解析相同格式——否则表单提交逻辑就断了。



















