Implementing Drag-and-Drop List Reordering in React with dnd-kit

To add drag-and-drop list reordering to React applications, follow these actionable steps:

  1. Install core dependencies: Use npm or yarn to add the base dnd-kit packages to you're project. The core library handles essential drag mechanics, and a sorting-specific package simplifies list rearrangement.
    npm install @dnd-kit/core @dnd-kit/sortable @dnd-kit/utilities
    
  2. Integrate sortable components and hooks: Import DndContext, SortableContext, useSortable, arrayMove, and DragOverlay from the respective packages.
  3. Configure drag context: Wrap your application or list container in DndContext to manage drag interactions, provide a unique ID list for SortableContext to track sortable elements, and handle the onDragEnd event.
  4. Build sortable items: Create a reusable SortableItem component that uses useSortable to attach drag handlers and refs.
  5. Render a drag overlay: Use DragOverlay to display a floating copy of the dragged element, improving UX.
  6. Update state with reordered data: Leverage arrayMove from @dnd-kit/utilities to safely rearrange your list state when dragging completes.

Here’s a restructured, functional code example:

import React, { useState } from 'react';
import { DndContext, closestCenter, KeyboardSensor, PointerSensor, useSensor, useSensors, DragOverlay } from '@dnd-kit/core';
import { arrayMove, SortableContext, sortableKeyboardCoordinates, verticalListSortingStrategy, useSortable } from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';

interface SortableTaskProps {
  id: string;
  content: string;
}

const SortableTask = ({ id, content }: SortableTaskProps) => {
  const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id });

  const dragStyle = {
    transform: CSS.Transform.toString(transform),
    transition,
    opacity: isDragging ? 0.3 : 1,
    padding: '12px',
    marginBottom: '8px',
    backgroundColor: '#f0f9ff',
    border: '1px solid #bae6fd',
    borderRadius: '8px',
    cursor: 'grab',
  };

  return (
    <div ref={setNodeRef} style={dragStyle} {...attributes} {...listeners}>
      {content}
    </div>
  );
};

const initialTaskList = [
  { id: 'task-write', content: 'Draft weekly report' },
  { id: 'task-code', content: 'Fix login validation' },
  { id: 'task-review', content: 'Review PR #241' },
];

const ReorderableTaskList = () => {
  const [tasks, setTasks] = useState(initialTaskList);
  const [activeTaskId, setActiveTaskId] = useState<string | null>(null);

  const sensors = useSensors(
    useSensor(PointerSensor),
    useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates })
  );

  const handleDragStart = (event: any) => {
    setActiveTaskId(event.active.id);
  };

  const handleDragEnd = (event: any) => {
    const { active, over } = event;
    if (over && active.id !== over.id) {
      setTasks((prev) => {
        const oldIdx = prev.findIndex((t) => t.id === active.id);
        const newIdx = prev.findIndex((t) => t.id === over.id);
        return arrayMove(prev, oldIdx, newIdx);
      });
    }
    setActiveTaskId(null);
  };

  const activeTask = activeTaskId ? tasks.find((t) => t.id === activeTaskId) : null;

  return (
    <div style={{ maxWidth: '400px', margin: '2rem auto', padding: '0 1rem' }}>
      <h2 style={{ textAlign: 'center', marginBottom: '1.5rem' }}>Priority Task List</h2>
      <DndContext
        sensors={sensors}
        collisionDetection={closestCenter}
        onDragStart={handleDragStart}
        onDragEnd={handleDragEnd}
      >
        <SortableContext items={tasks.map((t) => t.id)} strategy={verticalListSortingStrategy}>
          {tasks.map((task) => (
            <SortableTask key={task.id} id={task.id} content={task.content} />
          ))}
        </SortableContext>
        <DragOverlay>
          {activeTask ? (
            <div style={{ padding: '12px', backgroundColor: '#ffffff', border: '2px solid #0ea5e9', borderRadius: '8px', boxShadow: '0 4px 12px rgba(0,0,0,0.15)', cursor: 'grabbing' }}>
              {activeTask.content}
            </div>
          ) : null}
        </DragOverlay>
      </DndContext>
    </div>
  );
};

export default ReorderableTaskList;

This example adds keyboard accessibility support, uses a vertical sorting strategy, includes visual feedback for dragged items, and leverages arrayMove for safe state updates. The closestCenter collision detection ensures items reorder smoothly, and CSS.Transform handles positioning of sortable elements during drag operations.

Tags: React dnd-kit Drag and Drop list reordering accessibility

Posted on Fri, 08 May 2026 06:03:54 +0000 by mkarabulut