Skip to Content
Component ExamplesDrag & Drop

Overview

Drag and drop is a common interaction pattern for reordering lists, moving items between containers, and organizing content. However, drag operations require precise motor control and continuous pointer contact — making them inaccessible to users who rely on keyboards, switch devices, voice control, or head pointers. WCAG 2.2 introduced Success Criterion 2.5.7 Dragging Movements (AA), which requires that every operation achievable by dragging must also be achievable with a single-pointer alternative (such as click, tap, or keyboard). The simplest and most effective alternative for reorderable lists is “Move up” and “Move down” buttons, combined with an aria-live region to announce the result of each move.

WCAG Criteria:

  • 2.5.7 Dragging Movements  — any action achievable by dragging must also be achievable by a single pointer without dragging (Level AA, WCAG 2.2)
  • 2.1.1 Keyboard  — all functionality must be operable through a keyboard interface

Key requirements:

  • Every drag-to-reorder operation must have a single-pointer (click/tap) alternative
  • Keyboard users must be able to reorder items without a mouse
  • After a reorder action, announce the result to screen readers via aria-live
  • The deprecated aria-grabbed and aria-dropeffect attributes never had reliable screen reader support — do not use them
  • “Move up” / “Move down” buttons are the simplest and most universally understood alternative
  • Disable boundary buttons (first item cannot move up, last item cannot move down)

Reorderable List

Drag-Only Reorder vs. Button-Based Reorder with Announcements

Inaccessible
<!-- Drag-only reorder — no keyboard or single-pointer alternative --> <ul> <li draggable="true"> <span class="grip-handle">&#9776;</span> <span>Task 1</span> </li> <li draggable="true"> <span class="grip-handle">&#9776;</span> <span>Task 2</span> </li> <li draggable="true"> <span class="grip-handle">&#9776;</span> <span>Task 3</span> </li> <li draggable="true"> <span class="grip-handle">&#9776;</span> <span>Task 4</span> </li> </ul> <!-- No buttons, no keyboard handler — mouse drag is the only option -->
Live Preview

Drag items to reorder:

  • Task 1
  • Task 2
  • Task 3
  • Task 4

Mouse drag only. Keyboard and single-pointer users cannot reorder these items.

Accessible
<!-- Live region for announcements (exists in DOM before updates) --> <div aria-live="polite" class="sr-only" id="reorder-live"></div> <ul id="task-list"> <li> <span class="grip-handle">&#9776;</span> <span>Task 1</span> <button aria-label="Move Task 1 up" disabled>&uarr;</button> <button aria-label="Move Task 1 down" onclick="moveDown(0)">&darr;</button> </li> <li> <span class="grip-handle">&#9776;</span> <span>Task 2</span> <button aria-label="Move Task 2 up" onclick="moveUp(1)">&uarr;</button> <button aria-label="Move Task 2 down" onclick="moveDown(1)">&darr;</button> </li> <li> <span class="grip-handle">&#9776;</span> <span>Task 3</span> <button aria-label="Move Task 3 up" onclick="moveUp(2)">&uarr;</button> <button aria-label="Move Task 3 down" onclick="moveDown(2)">&darr;</button> </li> <li> <span class="grip-handle">&#9776;</span> <span>Task 4</span> <button aria-label="Move Task 4 up" onclick="moveUp(3)">&uarr;</button> <button aria-label="Move Task 4 down" disabled>&darr;</button> </li> </ul> <script> function moveUp(index) { var list = document.getElementById('task-list'); var items = list.children; list.insertBefore(items[index], items[index - 1]); announce(items[index], index); } function moveDown(index) { var list = document.getElementById('task-list'); var items = list.children; list.insertBefore(items[index + 1], items[index]); announce(items[index], index + 1); } function announce(item, newIndex) { var name = item.querySelector('span:nth-child(2)').textContent; document.getElementById('reorder-live').textContent = name + ' moved to position ' + (newIndex + 1); } </script>
Live Preview

Reorder tasks:

  • Task 1
  • Task 2
  • Task 3
  • Task 4

What’s wrong with the drag-only list?

  • draggable="true" only enables mouse drag — there is no keyboard equivalent built in
  • Keyboard users cannot reorder any items because there are no buttons or key handlers
  • Screen reader users cannot determine that the list is reorderable or perform reorder actions
  • Users of switch devices, head pointers, and voice control cannot perform drag gestures
  • The grip handle icon has no accessible name or role — it is meaningless to assistive technology
  • Violates WCAG 2.5.7 (no single-pointer alternative) and 2.1.1 (no keyboard access)

What the button-based alternative provides:

  • “Move up” and “Move down” buttons are keyboard-focusable and operable with Enter/Space
  • Each button has a descriptive aria-label (e.g., “Move Task 2 up”) so screen reader users know exactly what it does
  • Boundary buttons are disabled — the first item’s “up” button and last item’s “down” button are inactive, preventing invalid operations
  • An aria-live="polite" region announces the result after each move (e.g., “Task 2 moved to position 1”)
  • The live region exists in the DOM before content is injected, ensuring reliable screen reader announcements
  • Works with mouse click, keyboard, touch tap, switch devices, and voice control

What the screen reader announces:

VersionAnnouncement
Drag-only”Task 1” then “Task 2” etc. — no indication items are reorderable, no way to act
Button alternative”Task 2, Move Task 2 up button, Move Task 2 down button” then on click: “Task 2 moved to position 1”

Why not use aria-grabbed and aria-dropeffect?

  • Both attributes are deprecated in WAI-ARIA 1.1
  • They never had reliable screen reader support across any major browser/AT combination
  • No screen reader implemented a coherent drag-and-drop interaction model based on these attributes
  • Simple “Move up” / “Move down” buttons are universally understood and fully supported

Resources