aria-multiselectable仅是告知屏幕阅读器多选许可的语义标记,必须配合role="listbox"等合法容器使用,且需手动实现交互逻辑、状态同步(aria-selected)、键盘支持及焦点管理。

aria-multiselectable 不是加了就能多选,它只是一张“许可证”,告诉屏幕阅读器“这里允许多选”,但所有交互逻辑、状态同步、键盘支持都得你手写。
必须配 role="listbox" 才合法
单独在 <div> 或 <code><ul></ul> 上写 aria-multiselectable="true" 是无效的——ARIA 规范要求它只能出现在 role="listbox"、role="grid" 或 role="tree" 容器上。最常用的是 role="listbox":
<div role="listbox" aria-multiselectable="true" tabindex="0"> <div role="option" aria-selected="false" tabindex="0">苹果</div> <div role="option" aria-selected="true" tabindex="0">香蕉</div> </div>
- 不能用在
<select></select>上——原生multiple属性已自带语义,再加会冲突 - 不能用在
<ul></ul>上直接设该属性,除非你给它显式加role="listbox" -
role="listbox"必须有tabindex="0",否则无法键盘聚焦整个容器
aria-selected 必须手动同步,不能靠 class
aria-selected 是给辅助技术看的状态标记,不是视觉样式开关。JS 改变选中逻辑时,必须同步更新这个属性:
- 用户 Ctrl+Click 某项 → 找到对应 DOM,设
aria-selected="true"或"false" - Shift+↓ 连续选中 → 遍历区间内所有
role="option",逐个设aria-selected - 千万别只改 CSS 类(如
.is-selected),读屏器完全看不见 - 初始渲染建议显式写
aria-selected="false",避免某些读屏器对缺失属性解读不一致
键盘事件必须自己监听,浏览器不代劳
按 Shift+Arrow 或 Ctrl+Space 不会自动触发多选——这些全是你要监听的 keydown 事件:
- 用
event.preventDefault()阻止方向键默认滚动,否则焦点一动页面就跳 - 记录上一次聚焦项索引(
lastFocusedIndex),用于计算 Shift 区间 - Ctrl+Space 时,先确认当前是否有聚焦的
role="option",再 toggle 其aria-selected - 别在
keyup里处理 Ctrl/Shift —— 这些修饰键在keyup阶段常被系统吞掉,必须用keydown
容易被忽略的焦点和语义链
即使属性全对,焦点没管好,屏幕阅读器照样读不准:
- 每个
role="option"必须有tabindex="0"(或动态设tabindex="-1"+ JS.focus()) - 首次展开列表后,焦点要主动移到第一个(或上次选中的)
role="option"上 - 不要把
button塞进role="option"里——会破坏语义流,读屏器可能跳过或重复播报 - 如果用
role="grid"替代listbox,还得补aria-rowindex和aria-colindex,否则 JAWS 可能误判选中范围
真正麻烦的从来不是加几个属性,而是让每一次点击、每一下键盘、每一处焦点移动,都同步更新 DOM 属性、视觉样式和辅助技术状态——三者差一点,无障碍就断在那儿。











