原生 draggable="true" 必须禁用,因其绕过焦点流、无 aria 映射、datatransfer 对辅助技术不透明;合规方案是键盘驱动的“选中→移动→插入”三步操作,使用 role="list"/role="listitem"、aria-selected、aria-describedby 和 aria-live 实现 wcag 2.2 兼容。

原生 draggable="true" 不能直接用于可访问拖拽,它绕过焦点流、无 ARIA 映射、dataTransfer 对辅助技术不透明;合规路径是键盘驱动的“选中-移动-插入”两步操作,而非模拟鼠标行为。
为什么 draggable="true" 在无障碍场景下必须禁用
浏览器对 draggable="true" 的实现完全脱离键盘导航链:Tab 键进不去,Space/Enter 触发不了,屏幕阅读器读不出“可拖拽”状态,更不会播报位置变化。Chrome 和 Firefox 都未补全 ARIA 映射,role="application" 也兜不住。同时,dataTransfer 对辅助技术是黑盒,dragover 的视觉高亮也不会同步到 AT。
常见错误现象包括:键盘用户无法聚焦到可拖项、按空格后无任何反馈、排序完成后焦点丢失、屏幕阅读器静默不报位置变更。
- 所有使用
aria-dropeffect="move"的代码都属于过时写法(WAI-ARIA 1.2 已移除) -
aria-grabbed已被废弃,应改用aria-roledescription="可拖拽字段" - 纯
<div> 容器做排序区,会让辅助技术无法识别其语义目的 <h3>如何实现 WCAG 2.2 认可的键盘替代方案</h3> <p>核心不是“让拖拽支持键盘”,而是提供等效语义的操作路径:“选中 → 移动焦点 → 插入”。全程不启用 <code>draggable="true",避免干扰键盘逻辑。实操要点:
- 排序容器用
<ol role="list" aria-label="任务列表,支持重排序"></ol>,每项用<li role="listitem">,禁用项设aria-disabled="true"+tabindex="-1" - 按 Tab 进入列表,首个项需有
tabindex="0";按 Enter/Space 进入“选中态”,此时设aria-selected="true"并加视觉高亮 - 用 ↑/↓ 移动焦点时,实时更新
aria-describedby指向的提示文本,例如“插入到第 2 项之前” - 按
Shift+Enter确认插入,DOM 移动后立即调用el.focus()到新位置,并写入aria-live="polite"区域
keydown监听必须覆盖的关键键位与状态同步键盘交互失效往往不是没监听,而是监听时机或状态管理错位。方向键、Home/End、Esc、Enter 必须在正确上下文中响应,且需与内部状态机严格对齐。
容易踩的坑:
- 只监听
keyup:方向键松开时页面可能已滚动,导航逻辑被吞掉;必须用keydown+e.preventDefault() - 未区分“选中态”和“移动态”:↑/↓ 在未选中时应移动焦点,在已选中时才触发插入位置预览
- Esc 中断后未重置
draggable属性或残留aria-selected,导致后续操作错乱 - 未用单一状态机(如
{ idle, selected, moving, dropped })驱动所有分支,键盘说在拖,dragstart却没触发
性能提示:避免在
keydown中反复调用getBoundingClientRect()或 DOM 查询;焦点索引应缓存,位置提示文案可预生成。视觉反馈与焦点样式不可省略的细节
键盘用户依赖视觉线索判断当前状态,但仅靠 CSS
:focus会干扰鼠标用户,仅靠 outline 又常被全局清空——这是最常被跳过的合规红线。必须做到:
- 用
:focus-visible替代:focus,确保只有键盘聚焦时显示高对比边框或背景色 - 禁用
outline: none全局重置;若需自定义样式,必须提供同等或更高对比度的替代方案 - “插入预览”位置指示器不能只靠
:hover或dragover类名,键盘移动时需用 FLIP 技术驱动transform,否则动画会打断焦点流,且屏幕阅读器听不到位置变更 - 拖拽手柄图标(如三横线)必须带
aria-label="拖拽以重排序",不可仅靠视觉暗示
复杂点在于:所有视觉反馈必须与 ARIA 状态、DOM 顺序、焦点位置三者严格同步。差一个
el.focus()、少一句aria-live提示,就可能让屏幕阅读器用户卡在未知状态里。 - 排序容器用











