Skip to main content

Drag-and-drop library integrations

Keep your existing drag-and-drop implementation. Add OpenKeyNav as a small, direct keyboard path.

Your pointer library keeps ownership of dragging, collision detection, animation, previews, and touch behavior. OpenKeyNav selects the two explicit endpoints and translates those DOM elements into the same application operation your existing library adapter already calls.

OpenKeyNav
Provides direct keyboard selection of configured source and destination elements.
You
Connect those endpoints to the application's existing move operation, validation, focus, and feedback.

ResultPointer, touch, and OpenKeyNav keyboard routes complete the same application workflow.

pointer or touch → drag-and-drop library adapter ─┐
├→ application move operation → state / DOM
keyboard → OpenKeyNav callback ──────────┘

The sortable-library recipes below use one list. Choosing a destination means “move the selected item to the destination item's current position.” The interact.js recipe uses discrete dropzones. Both endpoint models route the interaction through an application operation instead of a fabricated library event.

Direct callback behavior

When a Move Mode configuration supplies callback, OpenKeyNav calls it directly, keeps the synthetic pointer and native drag-event path inactive, and leaves the host library's sensors idle. Omit callback when the interaction is designed for OpenKeyNav's native drag-event simulation path.

Versions and official patterns reviewed

These recipes were checked against the linked official documentation and these current package versions on August 2, 2026. Use the version column as the reviewed baseline and evaluate later library releases against their migration guidance.

LibraryReviewed package version
Pragmatic drag and drop@atlaskit/pragmatic-drag-and-drop@2.0.1, @atlaskit/pragmatic-drag-and-drop-hitbox@2.0.0
SortableJSsortablejs@1.15.7
dnd-kit, current API@dnd-kit/react@0.5.0
dnd-kit, legacy API@dnd-kit/core@6.3.1, @dnd-kit/sortable@10.0.0
React DnDreact-dnd@16.0.1
Angular CDK@angular/cdk@22.1.0
interact.jsinteractjs@1.10.28

Pragmatic drag and drop

This follows Atlassian's official examples and the simple-list source they link to: draggable and drop-target data flows into a top-level monitorForElements({onDrop}) handler.

Keep the edge-aware pointer path and extract its state update into moveTask. When no edge is supplied, the keyboard path infers the edge that moves the source to the target's current position.

TaskList.jsx
const moveTask = useCallback((sourceId, targetId, closestEdge) => {
setTasks((tasks) => {
const startIndex = tasks.findIndex((task) => task.id === sourceId);
const indexOfTarget = tasks.findIndex((task) => task.id === targetId);

if (startIndex < 0 || indexOfTarget < 0 || startIndex === indexOfTarget) {
return tasks;
}

return reorderWithEdge({
list: tasks,
startIndex,
indexOfTarget,
closestEdgeOfTarget:
closestEdge ?? (startIndex < indexOfTarget ? 'bottom' : 'top'),
axis: 'vertical',
});
});
}, []);

The existing monitor remains the pointer adapter. Keep the official type guards used by your application before consuming library data.

Existing Pragmatic DnD adapter
useEffect(() => {
return monitorForElements({
onDrop({source, location}) {
const target = location.current.dropTargets[0];
if (
!target ||
!isTaskData(source.data) ||
!isTaskData(target.data)
) {
return;
}

moveTask(
source.data.taskId,
target.data.taskId,
extractClosestEdge(target.data),
);
},
});
}, [moveTask]);

The officially linked task component already renders data-task-id. Point OpenKeyNav at that stable identifier and call the same operation.

OpenKeyNav addition
useEffect(() => {
const openKeyNav = new OpenKeyNav().init({
modesConfig: {
move: {
config: [{
fromElements: '[data-task-id]',
toElements: '[data-task-id]',
callback(source, destination) {
const sourceId = source.dataset.taskId;
const destinationId = destination.dataset.taskId;

if (sourceId && destinationId) {
moveTask(sourceId, destinationId);
}
},
}],
},
},
});

return () => {
openKeyNav.destroy();
};
}, [moveTask]);

Pragmatic drag and drop's accessibility guidance asks applications to provide an alternative control, communicate the outcome, and restore useful focus. OpenKeyNav supplies the endpoint-selection route; keep the application's validation, announcement, and focus behavior in the shared operation.

Add the config to one OpenKeyNav instance

The snippets show initialization beside the operation so the shared closure is visible. In the application, initialize one OpenKeyNav instance at the persistent boundary. Fold the relevant config entry into that initializer, or keep the shared move operation in a module/store that the root initializer can call.

If one page has multiple independent move experiences, put multiple objects in modesConfig.move.config, each with selectors scoped to its own container.

Validation and accessibility stay in the application

Place business validation in the shared operation so the drag-and-drop adapter and direct OpenKeyNav callback apply the same canDrop predicates, collision rules, authorization, and persistence behavior. Use selectors to offer valid endpoints from the start.

After integrating:

  1. Confirm pointer and touch behavior is unchanged.
  2. Turn OpenKeyNav on with Shift+O, press M, choose a source, and choose a destination.
  3. Verify the same data, persistence, validation, and undo behavior runs for both paths.
  4. Preserve useful focus after rerendering and communicate the completed move through the application's status or live-region pattern.
  5. Test the complete workflow with keyboard-only use, supported assistive technologies, and disabled users.

Complete the integrated interface with accurate semantics, focus management, validation, announcements, and outcome feedback.