The dnd-kit accessibility plugin provides comprehensive keyboard navigation and screen reader support out of the box.
Accessibility Plugin
The Accessibility plugin automatically adds ARIA attributes and screen reader announcements to your drag and drop interface:
import {DragDropProvider} from '@dnd-kit/react';
import {Accessibility} from '@dnd-kit/dom/plugins/accessibility';
function App() {
return (
<DragDropProvider
plugins={[
Accessibility, // Adds automatic accessibility features
]}
>
{/* Your draggable content */}
</DragDropProvider>
);
}
The Accessibility plugin is included by default in most dnd-kit setups. You typically don’t need to add it manually unless you’re customizing plugins.
What the Plugin Provides
The Accessibility plugin automatically adds:
ARIA Attributes
role="button" - Identifies draggable elements as interactive
aria-roledescription="draggable" - Describes the element’s drag capability
tabindex="0" - Makes elements keyboard-focusable
aria-pressed - Indicates drag state (true when dragging)
aria-grabbed - Alternative drag state indicator
aria-disabled - Indicates when dragging is disabled
aria-describedby - Links to screen reader instructions
Screen Reader Instructions
Default instructions are automatically provided:
“To pick up a draggable item, press the space bar. While dragging, use the arrow keys to move the item in a given direction. Press space again to drop the item in its new position, or press escape to cancel.”
Live Region Announcements
Screen readers announce drag and drop events in real-time:
- Drag start: “Picked up draggable item .”
- Drag over: “Draggable item was moved over droppable target .”
- Drag end: “Draggable item was dropped over droppable target .”
- Cancel: “Dragging was cancelled. Draggable item was dropped.”
Keyboard Navigation
The keyboard sensor enables full keyboard control:
Default Keyboard Controls
| Key | Action |
|---|
| Space or Enter | Pick up / Drop item |
| Arrow Keys | Move dragged item |
| Shift + Arrow Keys | Move faster (5x speed) |
| Escape | Cancel drag operation |
| Tab | Drop item and move focus |
Keyboard Sensor Configuration
Customize keyboard behavior with the KeyboardSensor:
import {KeyboardSensor} from '@dnd-kit/dom/sensors/keyboard';
const customKeyboardSensor = KeyboardSensor.configure({
// Distance to move per arrow key press
offset: 10, // or {x: 10, y: 15}
// Customize key codes
keyboardCodes: {
start: ['Space', 'Enter'],
cancel: ['Escape'],
end: ['Space', 'Enter', 'Tab'],
up: ['ArrowUp', 'KeyW'],
down: ['ArrowDown', 'KeyS'],
left: ['ArrowLeft', 'KeyA'],
right: ['ArrowRight', 'KeyD'],
},
});
<DragDropProvider sensors={[customKeyboardSensor]}>
{/* Your content */}
</DragDropProvider>
When customizing keyboard codes, ensure you maintain standard arrow key navigation for accessibility compliance.
Custom Announcements
Customize screen reader announcements to match your interface:
import {Accessibility} from '@dnd-kit/dom/plugins/accessibility';
const customAccessibility = new Accessibility(manager, {
announcements: {
dragstart({operation: {source}}) {
if (!source) return;
return `Started dragging ${source.data?.title || source.id}`;
},
dragover({operation: {source, target}}) {
if (!source || source.id === target?.id) return;
if (target) {
return `${source.data?.title} is now over ${target.data?.title}`;
}
return `${source.data?.title} is no longer over a drop zone`;
},
dragend({operation: {source, target}, canceled}) {
if (!source) return;
if (canceled) {
return `Cancelled. ${source.data?.title} was not moved.`;
}
if (target) {
return `${source.data?.title} was dropped on ${target.data?.title}`;
}
return `${source.data?.title} was dropped`;
},
},
// Debounce announcements during drag (default: 500ms)
debounce: 300,
});
Custom Screen Reader Instructions
Provide context-specific instructions:
const customAccessibility = new Accessibility(manager, {
screenReaderInstructions: {
draggable: `To reorder items in the list, press space to pick up an item.
Use arrow keys to move it up or down.
Press space again to place the item, or escape to cancel.`,
},
});
Focus Management
Ensure proper focus management during drag operations:
function SortableItem({id, index}) {
const [element, setElement] = useState(null);
const {isDragging, sortable} = useSortable({id, index, element});
// Return focus to element after drop
useEffect(() => {
if (!isDragging && element) {
const wasJustDropped = /* track previous state */;
if (wasJustDropped) {
element.focus();
}
}
}, [isDragging, element]);
return <div ref={setElement} tabIndex={0}>{id}</div>;
}
Preventing Keyboard Activation
Prevent keyboard sensor activation in specific scenarios:
const keyboardSensor = KeyboardSensor.configure({
preventActivation: (event, source) => {
// Don't activate if user is typing in an input
const target = event.target;
if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA') {
return true;
}
// Default behavior
return event.target !== source.handle && event.target !== source.element;
},
});
Accessible Drag Handles
When using drag handles, ensure they’re properly labeled:
function SortableWithHandle({id, index}) {
const [element, setElement] = useState(null);
const [handle, setHandle] = useState(null);
const {isDragging} = useSortable({id, index, element, handle});
return (
<div ref={setElement}>
<button
ref={setHandle}
aria-label={`Drag to reorder ${id}`}
aria-describedby="drag-instructions"
>
⋮⋮
</button>
<span>{id}</span>
</div>
);
}
Testing Accessibility
Keyboard Testing Checklist
Tab Navigation
Verify you can tab to all draggable items
Keyboard Dragging
Test dragging with Space/Enter and arrow keys
Escape Cancellation
Confirm Escape cancels the drag operation
Focus Management
Ensure focus returns to the correct element after drop
Screen Reader Testing
- Test with NVDA (Windows), JAWS (Windows), or VoiceOver (macOS)
- Verify announcements are clear and timely
- Ensure instructions are read when focusing items
- Check that drag state changes are announced
Best Practices
Provide Visual Focus IndicatorsAlways style :focus and :focus-visible states clearly:.draggable:focus-visible {
outline: 2px solid #4c9ffe;
outline-offset: 2px;
}
Use Semantic HTMLUse semantic elements when appropriate:// Good: Uses a button for the drag handle
<button ref={setHandle} aria-label="Drag to reorder">
<DragIcon />
</button>
// Avoid: Generic div without proper ARIA
<div ref={setHandle}>
<DragIcon />
</div>
Don’t Disable Keyboard AccessNever remove keyboard functionality for aesthetic reasons. If you customize sensors, always include the KeyboardSensor.
Reduced Motion Support
The library automatically respects prefers-reduced-motion:
// Transitions are automatically disabled when user prefers reduced motion
const {isDragging} = useSortable({
id,
index,
element,
transition: {
duration: 250,
easing: 'ease-out',
},
});
// When prefers-reduced-motion is enabled, duration becomes 0
Next Steps
Implement Keyboard Support
Optimize Announcements
Tune announcement timing and content for your use case
Test with Users
Conduct accessibility testing with real assistive technology users