Drag Drop List
DnD & InteractionReorderable list with drag-and-drop.
Preview
⠿[object Object]
⠿[object Object]
⠿[object Object]
⠿[object Object]
Usage
example.jsx
import { DragDropList } from "@/components/ui/drag-drop-list";
export default function Example() {
return <DragDropList />;
}Source Code
Copy this file into components/ui/drag-drop-list.jsx in your project.
drag-drop-list.jsx
"use client";
import { forwardRef, useState, useCallback } from "react";
import { cn } from "@/lib/utils";
const DragDropList = forwardRef(({ className, items: initialItems = [], onReorder, renderItem, ...props }, ref) => {
const [items, setItems] = useState(initialItems);
const [dragIdx, setDragIdx] = useState(null);
const [overIdx, setOverIdx] = useState(null);
const onDragStart = (i) => { setDragIdx(i); };
const onDragOver = (e, i) => { e.preventDefault(); setOverIdx(i); };
const onDrop = (i) => {
if (dragIdx === null || dragIdx === i) { setDragIdx(null); setOverIdx(null); return; }
const newItems = [...items];
const [moved] = newItems.splice(dragIdx, 1);
newItems.splice(i, 0, moved);
setItems(newItems);
onReorder?.(newItems);
setDragIdx(null);
setOverIdx(null);
};
return (
<div ref={ref} className={cn("space-y-1", className)} {...props}>
{items.map((item, i) => (
<div key={i} draggable
onDragStart={() => onDragStart(i)}
onDragOver={(e) => onDragOver(e, i)}
onDrop={() => onDrop(i)}
onDragEnd={() => { setDragIdx(null); setOverIdx(null); }}
className={cn("flex cursor-grab items-center gap-2 rounded-md border bg-background p-3 transition-all active:cursor-grabbing",
dragIdx === i && "opacity-50",
overIdx === i && dragIdx !== i && "border-primary"
)}
>
<span className="text-muted-foreground">⠿</span>
{renderItem ? renderItem(item, i) : <span className="text-sm">{String(item)}</span>}
</div>
))}
</div>
);
});
DragDropList.displayName = "DragDropList";
export { DragDropList };
Quick Install
Make sure you have the cn() utility set up. It requires clsx and tailwind-merge.
npm install clsx tailwind-merge