dnd-kit Reference
dnd-kit is a lightweight, modular drag and drop toolkit for React. It provides flexible primitives for building drag-and-drop interfaces.
Import
// Core
import { GripVertical, Plus } from "lucide-react";
interface Task {
id: string;
title: string;
}
function SortableTask({ task }: { task: Task }) {
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id: task.id });
const style: React.CSSProperties = {
transform: CSS.Translate.toString(transform),
transition: isDragging ? undefined : transition,
zIndex: isDragging ? 10 : undefined,
position: "relative",
};
return (
<div
ref={setNodeRef}
style={style}
className={`p-3 flex items-center gap-2 ${isDragging ? "opacity-80 shadow-lg" : ""}`}
>
<button {...attributes} {...listeners} className="cursor-grab active:cursor-grabbing touch-none">
<GripVertical className="w-4 h-4 text-muted-foreground" />
</button>
<span className="flex-1">{task.title}</span>
</div>
);
}
export default function App() {
const [tasks, setTasks] = usePersistentItem<Task[]>("tasks", []);
const [newTask, setNewTask] = React.useState("");
const addTask = () => {
if (!newTask.trim()) return;
setTasks(prev => [...prev, { id: crypto.randomUUID(), title: newTask.trim() }]);
setNewTask("");
};
const handleDragEnd = (event: DragEndEvent) => {
const { active, over } = event;
if (over && active.id !== over.id) {
setTasks(prev => {
const oldIndex = prev.findIndex(t => t.id === active.id);
const newIndex = prev.findIndex(t => t.id === over.id);
return arrayMove(prev, oldIndex, newIndex);
});
}
};
return (
<div>
<div>
<div>
<h3>Tasks</h3>
</div>
<div className="space-y-4">
<div className="flex gap-2">
<input
value={newTask}
onChange={e => setNewTask(e.target.value)}
placeholder="New task..."
onKeyDown={e => e.key === "Enter" && addTask()}
/>
<button onClick={addTask} size="icon">
<Plus className="w-4 h-4" />
</button>
</div>
<DndContext collisionDetection={closestCenter} onDragEnd={handleDragEnd}>
<SortableContext items={tasks} strategy={verticalListSortingStrategy}>
<div className="space-y-2">
{tasks.map(task => (
<SortableTask key={task.id} task={task} />
))}
</div>
</SortableContext>
</DndContext>
</div>
</div>
</div>
);
}
Smooth Drag Animation
For the smoothest drag experience, optimize the style handling:
function SortableItem({ item }: { item: Item }) {
const {
attributes,
listeners,
setNodeRef,
transform,
transition,
isDragging,
} = useSortable({ id: item.id });
// Optimized style for smooth dragging
const style: React.CSSProperties = {
// Use Translate instead of Transform - translate-only is GPU-accelerated
transform: CSS.Translate.toString(transform),
// Disable transition while dragging to prevent lag
transition: isDragging ? undefined : transition,
// Ensure dragged item renders above others
zIndex: isDragging ? 10 : undefined,
position: "relative",
};
return (
<div
ref={setNodeRef}
style={style}
className={`p-4 flex items-center gap-3 ${
isDragging
? "opacity-80 shadow-xl scale-[1.02]"
: "transition-colors duration-150 hover:bg-muted/50"
}`}
>
<button
{...attributes}
{...listeners}
className="cursor-grab active:cursor-grabbing touch-none"
>
<GripVertical className="w-5 h-5 text-muted-foreground" />
</button>
<span>{item.text}</span>
</div>
);
}
Key optimizations:
CSS.Translate.toString()- Use instead ofCSS.Transform.toString()for GPU-accelerated translate-only transforms- Disable transition while dragging -
transition: isDragging ? undefined : transitionprevents the drag animation from lagging - Explicit zIndex - Ensures the dragged item renders above siblings
- Avoid
transition-all- Use specific transitions liketransition-colorsonly when not dragging to avoid conflicts - Visual feedback - Add
scaleandshadoweffects to make the dragged item feel lifted
Best Practices
- Use
touch-noneon drag handles for proper touch behavior - Use
cursor-grabandactive:cursor-grabbingon drag handles for visual feedback - Use
CSS.Translate.toString()for smoother, GPU-accelerated transforms - Disable transitions while dragging to prevent animation lag
- Use
closestCenterfor vertical/horizontal lists - Keep IDs stable - use unique, persistent IDs for items
- Use
arrayMovehelper for reordering arrays