拖拽操作对键盘用户不可见,因html原生draggable仅响应鼠标事件、不触发键盘行为且未向辅助技术暴露语义;须手动实现键盘路径:聚焦→空格拾取→导航→回车放置,并同步aria-grabbed、aria-dropeffect、焦点与视觉反馈。

为什么拖拽操作对键盘用户完全不可见
HTML原生draggable属性只响应鼠标事件,不触发任何键盘行为,也不向辅助技术暴露语义。屏幕阅读器根本不知道某个div能拖、该往哪放、当前是否被选中——它只会读作“普通文本容器”。键盘用户按Tab停在上面,没有任何反馈,空格/回车也毫无反应,等于“可聚焦但不可操作”。
常见错误现象:
- 给
div加draggable="true"+tabindex="0",以为就万事大吉 - 只监听
dragstart/drop,没写keydown逻辑 - 用
aria-grabbed但没同步更新,状态始终为false
怎么让拖拽支持空格键“拾取”和回车键“放置”
必须手动补全键盘路径:聚焦 → 拾取 → 导航到目标 → 放置。不能依赖原生拖放事件。
实操要点:
- 为每个可拖拽项设
tabindex="0"+role="button"(不是role="gridcell",除非在表格语义里) - 监听
keydown:当event.key === ' '(注意是空格字符),调用e.preventDefault(),然后设element.setAttribute('aria-grabbed', 'true') - 为每个投放区设
tabindex="0"+role="region"+aria-dropeffect="move"(或"copy") - 在投放区上监听
keydown:当event.key === 'Enter'且存在已aria-grabbed="true"的元素时,执行移动逻辑并重置aria-grabbed - 用
aria-live="polite"区域播报状态,例如“项目‘标题A’已被选中”“已移动至第2位”
焦点管理最容易漏掉的两个时机
拖拽过程中焦点极易丢失,尤其在动态插入/删除DOM后。不处理,键盘用户会直接跳出交互流。
必须显式控制的节点:
- 拾取后:立即
focus()到第一个投放区,别等用户自己Tab过去 - 放置完成:把焦点移回刚被移动的元素(如果还存在),或回到原投放区下一个可聚焦项
- 若投放区是动态渲染(如React列表),确保
useEffect或connectedCallback里重新生成可聚焦元素列表,避免querySelectorAll拿到旧节点 - Safari下
focus({preventScroll: true})可能无效,需降级为focus()+element.scrollIntoView({ block: 'nearest' })
用CSS Grid做拖拽容器时的 tabindex 陷阱
给整个display: grid父容器加tabindex="-1",会导致所有子元素(包括内部button)彻底无法被Tab访问——浏览器跳过整棵子树。
正确做法:
- Grid容器本身**不要**设
tabindex,保持语义中立 - 每个可拖拽项用
role="row"(如果是行式布局)或保留原生语义(如button) - 单元格内交互控件(如编辑按钮)保持
tabindex="0"或原生可聚焦 - 需要语义化表格结构时,用
table+aria-labelledby,别硬套Grid
复杂点不在代码量,而在状态同步:aria-grabbed、aria-dropeffect、焦点位置、视觉反馈样式,四者必须严格一致。少同步一项,键盘用户就卡在黑盒里。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











