Skip to content
inklu
wcagwcag-2-2reactpointer-eventscomplianceengineering

Dragging Movements: the WCAG 2.2 criterion hiding in your kanban board

A deep dive on WCAG 2.2 success criterion 2.5.7, Dragging Movements — what counts as essential, the five patterns that fail it, and how to add a single-pointer path without ripping out the drag.

By Moe· Co-founder, Design8 min read

Open your product and try to reorder something with one finger, without holding it down. Move a card between columns. Reorder a playlist. Set a price range. Crop an avatar. If the only way to do any of those things is to press, hold, move, and release, you have a Level AA failure on your hands — and it is one that almost never shows up in an automated report, because the scanner has no way to know whether the alternative exists three menus away.

That is WCAG 2.2 success criterion 2.5.7, Dragging Movements. It is the least-discussed of the nine criteria added in 2.2, partly because it is easy to misread as "stop using drag and drop." It does not say that. It says drag cannot be the only way.

What 2.5.7 actually requires

The rule: all functionality that uses a dragging movement for operation can be achieved by a single pointer without dragging, unless dragging is essential or the functionality is determined by the user agent and not modified by the author.

Three things to pull apart there.

Dragging movement. This means a pointer-down, a continuous movement while held, and a pointer-up in a different place — where the path between matters. A press and release in one spot is not a drag. A swipe gesture on a carousel is a drag. Sliding a thumb along a track is a drag.

Single pointer without dragging. The alternative has to work with one pointer — a mouse click, a single tap, a stylus press. Not a keyboard. This trips people up constantly. Adding keyboard support to your kanban board is excellent work, and it satisfies 2.1.1 Keyboard, but it does not satisfy 2.5.7. Someone using a head-pointer, an eye-tracker, or a mouth stick has a pointer, not a keyboard, and holding a click steady while tracking a path is exactly the thing they cannot do reliably. You need a click-based path, not just a key-based one.

Essential. The escape hatch, and it is narrower than teams hope. Dragging is essential when removing it changes what the functionality fundamentally is. The canonical examples are freehand drawing, signature capture, and anything where the path itself is the data. Sorting a list is not essential drag — the outcome is an ordinal position, and an ordinal position can be set by clicking. "Our users expect it to feel like Trello" is not essentiality. Neither is "the drag is the whole point of the interaction," which is a statement about your design, not about the user's goal.

The user-agent exemption covers things the browser does on its own. Native text selection by dragging across a paragraph is not your failure. A <input type="range"> you have not re-implemented is not your failure — it already responds to a click anywhere on the track. The moment you build a custom slider out of divs and pointer events, you own it.

The five patterns that fail it

Sortable and reorderable lists. Kanban boards, playlist editors, form builders, dashboard widget grids, table column reordering. This is the biggest category by volume, and it is nearly universal in B2B software. Any list with a grab handle is a candidate.

Custom range and dual-thumb sliders. Price filters on e-commerce, date-range pickers on analytics dashboards, volume and timeline scrubbers. The single-thumb case is often fine if the track is click-to-set. Dual-thumb filters are much worse, because clicking the track is ambiguous — which thumb moves? — so most implementations disable track clicks entirely and leave dragging as the only path.

Map pan and zoom. Panning a map by dragging the canvas, with no directional controls and no address search, fails. Zoom by pinch or scroll-drag with no plus/minus buttons fails. Most mapping libraries ship the controls; plenty of teams hide them for visual cleanliness.

Image crop, rotate, and reposition. Avatar croppers, hero image focal-point pickers, PDF signature placement. The crop frame usually has to be moved by drag. Worth noting: the freehand annotation on top of an image may well be essential, while moving the crop box almost certainly is not.

Drag-to-upload zones. These usually pass, because the dropzone is nearly always paired with a "browse files" button that opens the native picker. It is the exception in this list rather than an offender — but check that the button is a real button and not just decorative text next to an invisible file input.

The fix is an alternative path, not a rewrite

The instinct is to rip out the drag library. Do not. Keep the drag for the people who like it and add a click-based path beside it. Three patterns cover almost everything.

Pattern 1: click to pick up, click to place

This is the strongest general answer for sortable lists, and it maps cleanly onto how most drag libraries already model state. Clicking a handle enters a "carrying" mode; every valid destination becomes a click target; a second click drops the item. Escape or a second click on the original spot cancels.

function SortableCard({ card, index, carrying, onPickUp, onDropAt, onCancel }) {
  const isCarrying = carrying?.id === card.id

  return (
    <li>
      {carrying && !isCarrying && (
        <button
          className="drop-target"
          onClick={() => onDropAt(index)}
        >
          Move {carrying.title} here
        </button>
      )}

      <article>
        <h3>{card.title}</h3>
        <button
          aria-pressed={isCarrying}
          onClick={() => (isCarrying ? onCancel() : onPickUp(card))}
        >
          {isCarrying ? `Cancel move of ${card.title}` : `Move ${card.title}`}
        </button>
      </article>
    </li>
  )
}

Two details matter more than the mechanics. The drop targets must be real, focusable, clickable elements with accessible names that say where the item is going — not bare divs with click handlers, which fail 4.1.2 while you are busy fixing 2.5.7. And the state change needs an announcement, so a role="status" live region saying "Picked up Card A, position 2 of 5" while carrying, and "Moved Card A to Done, position 1 of 3" on drop.

Pattern 2: a move menu

Lower effort, and often better for long lists where the destination is off-screen. The handle opens a menu: Move up, Move down, Move to top, Move to bottom, Move to column. No carrying state to manage, no drop zones to render, and it works identically for pointer and keyboard users. For a backlog of two hundred items, this beats pick-up-and-place, because nobody wants to click through forty drop targets.

Pattern 3: make the track clickable

For sliders, the single-pointer alternative is a click on the track that moves the nearest thumb. For dual-thumb ranges, pair it with numeric inputs so a value can be typed directly. The cheapest fix of all, if you can take it, is to stop hand-rolling and build on <input type="range"> — the native element handles click-to-set, keyboard, and touch without any of this work.

/* Give the thumb and track enough of a hit area to satisfy 2.5.8 too */
.slider-thumb {
  min-inline-size: 24px;
  min-block-size: 24px;
}

While you are in there: 2.5.7 and 2.5.8 Target Size travel together. Drag handles are usually tiny, and a six-pixel grip dot that you have just made clickable is now an interactive target that needs 24 by 24 CSS pixels of hit area. Fixing the first violation and creating the second is a bad trade.

Why scanners miss this one

I would rather be straight about where automation stops on this criterion, because the gap is real.

A scanner can reliably find the suspects. Source-level signals are strong: imports of dnd-kit, react-dnd, react-beautiful-dnd, sortablejs, or interact.js; handlers for onDragStart, onPointerDown paired with onPointerMove; elements carrying draggable="true"; CSS touch-action: none on an interactive element, which is a near-tell for a custom pointer-drag implementation. Any of those means a dragging interaction exists in the file, with high confidence.

What a scanner cannot decide on its own is whether an acceptable alternative exists somewhere else in the interface, and whether the drag is essential. Those are judgment calls about intent, and they need a person. So the useful output is not a pass or a fail — it is a list of exactly which components in your codebase implement a drag, so a human can answer two questions per component instead of auditing the whole application:

  1. Can a user complete this with single clicks or taps, no holding, no path?
  2. If not, is the path itself the data?

That is usually a twenty-minute review for an application that would take two days to audit blind. And because the signals are structural, the review can happen against source before anything is deployed — the same argument as the audit-to-remediation gap that eats most accessibility budgets: knowing is cheap, fixing is not, and the closer the finding sits to the code the cheaper the fix gets.

This is the shape of work inklu is built for. We scan HTML, CSS, JSX, and SCSS against WCAG 2.2 with axe-core v4.11 plus 50-plus rules of our own, surface the components where a drag is the only path, and open a GitHub pull request with the alternative wired in — drop targets, live-region announcements, hit areas and all. You review the diff like any other PR, and nothing lands on your default branch without your approval. Token costs for scans and fixes are on the pricing page, and the mechanics are in the FAQ.

Where to start this week

Grep your codebase for draggable, onDragStart, and touch-action: none. Every hit is a component to check. Then take the top three by traffic and try to use them with a single finger, no holding — the way a user with a tremor, a head-pointer, or a hand that does not cooperate today would have to.

If you cannot finish the task, neither can they.

Book a demo at inklu.io to see the scan run against your repo, or email hello@inklu.io if you want to talk through a drag interaction you think might be essential.