Operable / 2.5 Input Modalities

2.5.7Dragging Movements

Level AANew in 2.2

For drag-based operations (sliders, reordering, boundary resizing), provide an alternative that does not require dragging, such as clicks or buttons.

New success criterion added in WCAG 2.2

This criterion was introduced in WCAG 2.2 and was not part of WCAG 2.1. Refer to the links below for detailed guidance.

Why are dragging alternatives needed?

Dragging a slider thumb, reordering a list, moving a pane boundary —"press-and-move" operations (dragging) are difficult or impossible for the following people:

  • People with hand tremors (essential tremor, Parkinson's disease, etc.): They cannot maintain pressure while moving precisely. Single-tap or single-click button operations are manageable.
  • People with unstable mouse control or difficulty with trackpads: The pointer may slip off during a drag, causing the operation to fail midway.
  • People using switch devices (operating a PC with 1-2 switches): Drag operations are simply not possible.

WCAG 2.5.7 requires that all operations achievable by dragging also have a non-drag alternative (a single click/tap, or dedicated buttons).

Note

This is a separate requirement from "keyboard operable (SC 2.1.1)."SC 2.5.7 targets people who use a pointer (mouse/touch) but find dragging difficult. Providing button or track-click alternatives satisfies 2.5.7. Adding keyboard alternatives as well covers 2.1.1 simultaneously.

Tip

Exception: When the drag path itself is the essential function (e.g., freehand drawing, tracing a map boundary), no alternative is required. Sliders, reordering, and resize handles — where only the final position matters — are not exempt.

Failing Example (Drag-only)

The slider below changes its value only by dragging the thumb. There are no +/− buttons and no track-click functionality. People with hand tremors or switch device users cannot set the value accurately.

Drag-only slider (2.5.7 Fail)
Volume

The thumb can only be moved by dragging. People who have difficulty dragging cannot operate it.

<!-- ❌ Drag-only: no alternative provided -->
<span class="sv-bad-label">Volume</span>
<div class="sv-bad-track">
  <!-- No tabindex or role → can't be focused via keyboard -->
  <div class="sv-bad-thumb" id="bad-knob"></div>
</div>
<!-- No +/− buttons, no track click support -->

Bad / Avoid

Problems with this implementation:

  • No drag alternative — No buttons or track click; people with tremors or switch users cannot operate it.
  • Not keyboard operable — No role="slider" or tabindex; keyboard operation is impossible.
  • Current value not conveyed — No aria-valuenow; screen readers cannot announce the value.
  • Role not conveyed — No role="slider"; it is not recognized as a slider.

Passing Example (Button, Click, and Keyboard Alternatives)

The slider below can be operated in 4 different ways. Users who have difficulty dragging can choose the method that works best for them.

Slider with drag + button + click + keyboard support (2.5.7 Pass)
Volume40

Operable via drag, +/− buttons, track click, or keyboard.

Tip

When a screen reader focuses the thumb, it announces "Volume, slider, 40, minimum 0, maximum 100" — the role, current value, and range. Because aria-valuenow is updated on every operation, changes are immediately communicated to assistive technology.

Keyboard Operation

KeyActionRequired / Optional
/ Increase value by one stepRequired
/ Decrease value by one stepRequired
HomeSet to minimum value (0)Required
EndSet to maximum value (100)Required
Page Up / Page DownIncrease/decrease by a large step (10)Optional (recommended)

Note

Keyboard operation is also a requirement of WCAG 2.1.1 "Keyboard." SC 2.5.7 specifically requires button/click pointer alternatives, but implementing both covers a wider range of users. Using <input type="range"> provides keyboard operation automatically via the browser.

Implementation (Code)

Good / Recommended

Provide 4 input paths — drag (Pointer Events), track click, +/− buttons, and keyboard — all calling the same setValue(). Always update aria-valuenow on every operation.

Markup:

<!-- Drag + click + buttons + keyboard alternatives -->
<div class="sv-wrap">
  <div class="sv-header">
    <span id="d257-vol-label">Volume</span>
    <span id="d257-vol-out" class="sv-val">40</span>
  </div>
  <div class="sv-row">
    <button type="button" class="sv-btn" id="d257-minus"
            aria-label="Decrease volume">−</button>

    <div class="sv-track" id="d257-track">
      <div class="sv-fill" id="d257-fill" style="width:40%"></div>
      <div role="slider"
           tabindex="0"
           id="d257-thumb"
           class="sv-thumb"
           aria-labelledby="d257-vol-label"
           aria-valuemin="0"
           aria-valuemax="100"
           aria-valuenow="40"
           style="left:40%"></div>
    </div>

    <button type="button" class="sv-btn" id="d257-plus"
            aria-label="Increase volume">+</button>
  </div>
</div>

Script:

const track = document.getElementById('d257-track');
const thumb = document.getElementById('d257-thumb');
const fill  = document.getElementById('d257-fill');
const out   = document.getElementById('d257-vol-out');
const minus = document.getElementById('d257-minus');
const plus  = document.getElementById('d257-plus');

const MIN = 0, MAX = 100, STEP = 1, BIG = 10;

function getValue() {
  return Number(thumb.getAttribute('aria-valuenow'));
}
function setValue(next) {
  const v = Math.min(MAX, Math.max(MIN, Math.round(next)));
  const pct = ((v - MIN) / (MAX - MIN)) * 100;
  // Screen reader feedback: always update on each operation
  thumb.setAttribute('aria-valuenow', String(v));
  thumb.style.left = pct + '%';
  fill.style.width  = pct + '%';
  out.textContent   = String(v);
}

/* ① Drag — Pointer Events + setPointerCapture */
// setPointerCapture keeps tracking even if the finger leaves the element on touch/stylus
thumb.addEventListener('pointerdown', (e) => {
  e.preventDefault();
  thumb.setPointerCapture(e.pointerId);
});
thumb.addEventListener('pointermove', (e) => {
  if (!thumb.hasPointerCapture(e.pointerId)) return;
  const { left, width } = track.getBoundingClientRect();
  setValue(((e.clientX - left) / width) * MAX);
});

/* ② Track click to move (2.5.7 alternative) */
track.addEventListener('click', (e) => {
  if (e.target === thumb) return; // Ignore clicks on the thumb itself
  const { left, width } = track.getBoundingClientRect();
  setValue(((e.clientX - left) / width) * MAX);
  thumb.focus(); // Move focus to the thumb
});

/* ③ +/− buttons (2.5.7 alternative) */
minus.addEventListener('click', () => setValue(getValue() - STEP));
plus.addEventListener('click',  () => setValue(getValue() + STEP));

/* ④ Keyboard (APG role="slider" compliant) */
thumb.addEventListener('keydown', (e) => {
  const v = getValue();
  const map = {
    ArrowRight: v + STEP, ArrowUp:   v + STEP,
    ArrowLeft:  v - STEP, ArrowDown: v - STEP,
    PageUp:     v + BIG,  PageDown:  v - BIG,
    Home: MIN,  End: MAX,
  };
  if (!(e.key in map)) return;
  e.preventDefault();
  setValue(map[e.key]);
});

setValue(40); // Initialize and sync UI with aria

Checklist

  • Operations achievable by dragging have button or click alternatives
  • Alternatives use a single pointer action (click/tap) without dragging
  • Alternatives achieve the same result as dragging (precision and range)
  • The slider thumb has role="slider" and tabindex="0"
  • aria-valuemin / aria-valuemax / aria-valuenow are correctly set
  • aria-valuenow is updated every time the value changes
  • Keyboard Home End work (also satisfies 2.1.1)
  • The thumb has touch-action: none to prevent conflicts with scrolling
  • Focus rings are not removed (outline: none not used)
  • Pointer capture is maintained even if the finger leaves the element during drag (setPointerCapture)
  • Operations where the drag path itself is essential (e.g., freehand drawing) are understood as exceptions

Normative References