原生需配合label[for]与id显式关联以保障可访问性;移除选项须从dom删除或加aria-hidden="true"和tabindex="-1";键盘操作需支持全链路导航;name应为复数加方括号如category[];ios safari不支持multiple,需降级方案。

multiple 属性必须配合
原生 <select multiple></select> 在可访问性上不是“开了 multiple 就完事”。屏幕阅读器依赖 label 与 select 的显式关联才能正确播报控件作用。如果只靠 placeholder 或视觉文本,读屏软件大概率跳过或误读。
实操建议:
-
<label for="skills">擅长技能</label><select id="skills" multiple>…</select>是最低要求,for和id必须严格匹配 - 避免用
<label><select></select></label>包裹写法——部分旧版 JAWS 不识别这种隐式关联 - 若表单字段动态生成,确保每次插入新
select时都生成唯一id并同步更新label[for]
选中项标签移除逻辑不能只靠视觉隐藏
用户点击 × 移除某个已选项时,常见错误是仅用 display: none 或 visibility: hidden 隐藏对应 <span></span> 标签。这会导致屏幕阅读器仍能聚焦到该元素,但无法操作,形成“幽灵焦点”。
正确做法是彻底从 DOM 移除或用无障碍方式隐藏:
- 移除操作应调用
element.remove(),而非仅加样式隐藏 - 若需保留 DOM 结构(如做动画),必须同时加
aria-hidden="true"和tabindex="-1" - 每次移除后,触发
aria-live="polite"区域播报“已移除 JavaScript”,否则读屏用户不知道状态变化
键盘操作必须支持全链路导航与选择
<select multiple></select> 原生支持 Tab 进入、Arrow 上下切换、Space / Enter 切换选中,但自定义多选下拉(比如用 <div> 模拟)常丢失这部分逻辑。一旦键盘不可用,就直接违反 WCAG 2.1 AA 级标准。
<p>关键点:</p>
<ul>
<li>容器需有 <code>role="listbox",每个选项用 role="option",并设 aria-selected 实时同步状态
Home/End 跳首尾,PageUp/PageDown 翻页(尤其选项 >10 时)Ctrl+A(Windows)或 Cmd+A(macOS)应全选,而非触发浏览器默认全选页面文本Tab 进入,且 Enter 或 Space 触发删除,不能只靠 click
提交值格式影响后端解析,别忽略 name 属性命名
前端多选提交的字段名,直接影响后端能否正确接收数组。常见坑是写成 name="category",结果 PHP/Node.js 只收到最后一个选中项。
解决方案很直接:
- 始终用复数 + 方括号:
name="category[]"(PHP)、name="category"(但后端需声明为数组类型,如 Express 中用req.body.category并确保是数组) - 避免空格或特殊字符:
name="user roles"会被 URL 编码破坏,改用name="user_roles" - 如果用 FormData 提交,确认
append时传的是多个同名键值对,而不是拼接字符串
移动端 Safari 完全忽略 multiple 的交互逻辑,所以真要兼容 iOS,不能只靠原生 <select multiple></select> —— 得换方案,或者至少 fallback 到全选模式+滚动列表。











