aria-grabbed and aria-dropeffect Deprecation
Permalink to "aria-grabbed and aria-dropeffect Deprecation"aria-grabbed and aria-dropeffect were ARIA 1.0’s attempt to make drag and drop accessible: mark the dragged element as grabbed, mark drop targets with the effect a drop would have, and let assistive technology do the rest. Assistive technology never did. Screen reader support was minimal, browsers mapped the attributes inconsistently, and ARIA 1.1 deprecated both, stating that a future version would replace them. That replacement has not arrived, so code that still uses them is relying on attributes no reader announces.
This guide explains what to use instead. It belongs to row drag & drop reordering.
Spec reference
Permalink to "Spec reference"- WAI-ARIA 1.1 and 1.2 —
aria-grabbedandaria-dropeffectare deprecated. Authors are advised not to use them. Validators such as axe-core report them as deprecated attributes. - SC 4.1.2 Name, Role, Value (A) — whatever state you expose must be programmatically determinable through supported properties: name,
aria-pressed,aria-selected, descriptions. - SC 4.1.3 Status Messages (AA) — pick-up, position changes and drop outcomes are status messages.
- SC 2.5.7 Dragging Movements (AA) — independent of ARIA attributes, a non-drag alternative is required.
When to use — and when not to
Permalink to "When to use — and when not to"Use the replacements whenever a table or list supports drag-and-drop: a handle button whose pressed state means “picked up”, a description explaining the keys, and a live region narrating the move. Libraries such as dnd-kit and Atlassian’s Pragmatic drag and drop follow exactly this model.
Do not keep aria-grabbed “just in case”. It adds validator noise and gives developers the false impression that the drag is accessible. Do not invent your own aria-* attributes for drag state either; unknown attributes are ignored.
The misapplication: setting aria-dropeffect="move" on every row while dragging and assuming screen reader users now know where they can drop. They do not; nothing is announced.
Annotated code example
Permalink to "Annotated code example"<p id="dnd-help" class="visually-hidden">
Press Space to pick up. Use Up and Down arrows to move. Press Space to drop or Escape to cancel.
</p>
<p id="dnd-live" aria-live="assertive" class="visually-hidden"></p> <!-- narration (SC 4.1.3) -->
<tr data-id="t7">
<td>
<!-- Handle is a real button; pressed = picked up (SC 4.1.2) -->
<button type="button" class="drag-handle"
aria-label="Reorder Design review" aria-describedby="dnd-help" aria-pressed="false">
<svg viewBox="0 0 20 20" aria-hidden="true" focusable="false">…</svg>
</button>
</td>
<th scope="row">Design review</th>
</tr>
handle.addEventListener('keydown', (e) => {
const picked = handle.getAttribute('aria-pressed') === 'true';
if (e.key === ' ' || e.key === 'Enter') {
e.preventDefault();
if (!picked) { handle.setAttribute('aria-pressed', 'true'); live.textContent = `Picked up Design review. Position ${pos} of ${n}.`; }
else { drop(); handle.setAttribute('aria-pressed', 'false'); live.textContent = `Dropped Design review at position ${pos} of ${n}.`; }
} else if (picked && (e.key === 'ArrowUp' || e.key === 'ArrowDown')) {
e.preventDefault();
pos = moveBy(e.key === 'ArrowUp' ? -1 : 1);
live.textContent = `Position ${pos} of ${n}.`; // short updates while moving
} else if (picked && e.key === 'Escape') {
cancel(); handle.setAttribute('aria-pressed', 'false');
live.textContent = `Cancelled. Design review returned to position ${origin} of ${n}.`;
}
});
Assertive politeness is used here deliberately: during a keyboard drag, each message is the direct response to a keypress and stale messages are useless.
Keyboard & AT behaviour
Permalink to "Keyboard & AT behaviour"| Key | Action | Expected announcement | AT deviations |
|---|---|---|---|
| Tab to handle | — | “Reorder Design review, toggle button, not pressed” + help | VoiceOver reads the description after a pause |
| Space | Pick up | “Picked up Design review. Position 6 of 12.” | JAWS may say “pressed” first |
| Down arrow | Move | “Position 7 of 12.” | Fast presses may queue; keep messages short |
| Space | Drop | “Dropped Design review at position 7 of 12.” | — |
| Escape | Cancel | “Cancelled. Design review returned to position 6 of 12.” | — |
Integration context
Permalink to "Integration context"The keyboard drag mode narrated here is detailed further in announcing drag and drop moves with live regions. It complements, and does not replace, keyboard row reordering with move buttons. Choosing between assertive and polite narration is discussed in choosing between polite and assertive aria-live regions.
Gotchas
Permalink to "Gotchas"Handles inside a grid. In a role="grid", the handle is a control inside a cell; arrow keys already move between cells. While picked up, arrows must move the row, not the focus — and the grid’s own key handler must stand down.
HTML5 draggable. Setting draggable="true" on rows does nothing for keyboard or screen reader users and can make some readers announce “draggable”. It is harmless but not an accessibility feature.
Linters. axe-core flags deprecated ARIA via aria-deprecated-role/attribute checks in recent versions; treat the warning as a prompt to implement the replacements, not just to delete the attribute.
Framework notes
Permalink to "Framework notes"dnd-kit (React) implements this model: its KeyboardSensor handles Space, arrows and Escape, and its accessibility.announcements option lets you write the pick-up, move and drop messages. SortableJS has no keyboard support; add move buttons. Angular CDK drag-drop supports keyboard dragging in recent versions, but check announcements — they are not provided by default — and use LiveAnnouncer. Vue Draggable (based on SortableJS) also needs move buttons.
// dnd-kit: custom announcements that use item names and positions
const announcements = {
onDragStart: ({ active }) => `Picked up ${name(active.id)}. Position ${pos(active.id)} of ${n}.`,
onDragOver: ({ over }) => over ? `Position ${pos(over.id)} of ${n}.` : 'Not over a valid position.',
onDragEnd: ({ active, over }) => `Dropped ${name(active.id)} at position ${pos(over.id)} of ${n}.`,
onDragCancel: ({ active }) => `Cancelled. ${name(active.id)} returned.`,
};
Testing checklist
Permalink to "Testing checklist"FAQ
Permalink to "FAQ"Is there a modern ARIA replacement for aria-grabbed?
Not a dedicated attribute. ARIA 1.1 deprecated it with the intent of a future replacement that has not been specified. Supported properties and live regions are the current answer.
Should I remove aria-grabbed from a component that works?
Yes. It does not contribute to accessibility, and removing it clarifies that the real behaviour comes from the handle, description and announcements.
Can aria-selected express "picked up" instead of aria-pressed?
In a listbox or grid where selection already means something else, no — it would conflict. aria-pressed on a dedicated handle button keeps the meanings separate.
Do I still need move buttons if keyboard dragging works well?
Yes, for single-pointer users under 2.5.7, and because many users find buttons more predictable than a mode.
Will a future ARIA version bring back a drag-and-drop attribute?
Possibly, but nothing is specified, and support would take years to reach users. Build with supported semantics now; if a new attribute appears, it will be an addition rather than a replacement for clear names and announcements.
Does draggable="true" conflict with screen readers?
It does not break anything, but some readers announce “draggable”, which promises an interaction keyboard users cannot perform. Keep it for the pointer drag, and make sure the handle’s description explains the keyboard alternative.
Related
Permalink to "Related"- Announcing drag and drop moves — narration in depth
- Move buttons — the non-drag alternative
- dnd-kit sortable rows — a library that follows this model