A flexible, lightweight drag-and-drop toolkit for React. Build sortable lists, grids, and Kanban boards with a controlled API and minimal overhead.
react-dragdrop-kit is designed around a controlled data model:
- You own state.
- The library reports drag intent and reorder results.
- You decide what to persist and render.
This keeps behavior predictable and easy to integrate with app-specific rules.
- Vertical and horizontal list support
- Controlled reorder callback:
onReorder(newItems, orderUpdates) - Optional visual drop indicator
- Optional custom drag preview style/class
- Optional handle-only dragging via
dragHandle - Optional multi-item drag with
selectedIds+multiDragEnabled - Optional live list reordering during drag-over via
liveReorder
- Card reorder within columns
- Card movement across columns
- Column reordering
- Headless rendering (
renderColumn,renderCard) - Accessibility helpers (
AnnouncerProvider,useAnnouncer,announcements) - Keyboard drag-reorder is planned, not fully shipped yet
- Live-reorder preview for Kanban is not shipped yet (drop-commit only)
- TypeScript-first API
- Lightweight runtime and tree-shakeable exports
- Built on
@atlaskit/pragmatic-drag-and-drop
npm install react-dragdrop-kit
# or
pnpm add react-dragdrop-kit
# or
yarn add react-dragdrop-kitimport { DragDropList } from "react-dragdrop-kit";import { KanbanBoard, applyDragResult } from "react-dragdrop-kit/kanban";import { useState } from "react";
import { DragDropList } from "react-dragdrop-kit";
interface Todo {
id: string;
position: number;
title: string;
}
export default function TodoList() {
const [items, setItems] = useState<Todo[]>([
{ id: "1", position: 0, title: "Design" },
{ id: "2", position: 1, title: "Build" },
{ id: "3", position: 2, title: "Ship" },
]);
return (
<DragDropList
items={items}
onReorder={(next) =>
setItems(next.map((item, index) => ({ ...item, position: index })))
}
renderItem={(item) => (
<div style={{ padding: 12, border: "1px solid #e5e7eb", borderRadius: 8 }}>
{item.title}
</div>
)}
showDropIndicator
gap={8}
/>
);
}<DragDropList
items={items}
onReorder={handleReorder}
renderItem={renderItem}
dragHandle="[data-drag-handle]"
selectedIds={selectedIds}
multiDragEnabled
liveReorder
/>Behavior notes:
dragHandleis optional. If provided, drag starts only from matching descendants.multiDragEnabledis opt-in. Without it, behavior remains single-item drag.selectedIdsis consumed only when multi-drag is enabled.liveReorderis opt-in. Without it, reorder commits on drop (existing behavior).
import { useCallback, useState } from "react";
import {
KanbanBoard,
applyDragResult,
type DropResult,
type KanbanBoardState,
} from "react-dragdrop-kit/kanban";
export default function Board() {
const [state, setState] = useState<KanbanBoardState>({
columns: [
{ id: "todo", title: "To Do", cardIds: ["task-1", "task-2"] },
{ id: "done", title: "Done", cardIds: [] },
],
cards: {
"task-1": { id: "task-1", title: "Design landing page" },
"task-2": { id: "task-2", title: "Implement auth" },
},
});
const handleDragEnd = useCallback(
(result: DropResult, stateBefore: KanbanBoardState) => {
if (!result.destination) return;
setState(applyDragResult(stateBefore, result));
},
[]
);
return (
<KanbanBoard
state={state}
onDragEnd={handleDragEnd}
renderColumn={(column) => <div style={{ padding: 12 }}>{column.title}</div>}
renderCard={(card) => <div style={{ padding: 12 }}>{card.title}</div>}
/>
);
}type DraggableItem = {
id: string;
position: number;
[key: string]: any;
};
type OrderUpdate = {
id: string;
newPosition: number;
moved?: boolean;
};| Prop | Type | Default | Description |
|---|---|---|---|
items |
T[] |
Required | Controlled items. Each item must include id and position. |
onReorder |
(newItems: T[], orderUpdates: OrderUpdate[]) => void |
Required | Fired after successful drop/reorder. |
renderItem |
(item: T, index: number) => ReactNode |
Required | Item renderer. |
containerClassName |
string |
"" |
Class name for container. |
containerStyle |
React.CSSProperties |
{} |
Inline style for container. |
itemClassName |
string |
"" |
Class for each draggable wrapper. |
itemStyle |
React.CSSProperties |
{} |
Style for each draggable wrapper. |
dragPreviewClassName |
string |
"" |
Class for generated drag preview. |
dragPreviewStyle |
React.CSSProperties |
{} |
Style for generated drag preview. |
onDragStart |
(item: T, index: number) => void |
undefined |
Callback on item drag start. |
onDragEnd |
(item: T, index: number) => void |
undefined |
Callback on item drag end. |
disabled |
boolean |
false |
Disables list drag/drop. |
gap |
number | string |
undefined |
Gap applied to container. |
direction |
"vertical" | "horizontal" |
"vertical" |
Layout and closest-edge interpretation. |
showDropIndicator |
boolean |
false |
Enables drop indicator line. |
dropIndicatorClassName |
string |
"" |
Class for drop indicator. |
dropIndicatorStyle |
React.CSSProperties |
{} |
Style for drop indicator. |
dropIndicatorPosition |
"top" | "bottom" |
"bottom" |
Indicator position for hovered target item. |
dragHandle |
string |
undefined |
CSS selector for handle-only dragging. |
selectedIds |
string[] |
[] |
Selected IDs used by multi-drag. |
multiDragEnabled |
boolean |
false |
Enables grouped drag behavior. |
liveReorder |
boolean |
false |
Reorders list in real time during drag-over. |
The full Kanban API is documented in docs/kanban.md.
Exports:
- Components:
KanbanBoard,KanbanColumnView,KanbanCardView - Hooks:
useKanbanDnd,useAutoscroll - A11y:
AnnouncerProvider,useAnnouncer,announcements - Utils:
applyDragResult,reorderArray - Types:
KanbanBoardState,DropResult,KanbanCard,KanbanColumn, and more
examples/basic-example.tsxexamples/advanced-features.tsxexamples/material-ui-example.tsxexamples/tailwind-example.tsxexamples/kanban/basic-kanban.tsxexamples/kanban/rich-cards-kanban.tsxexamples/kanban/themed-kanban.tsxexamples/kanban/accessible-kanban.tsx
- List/grid examples under
apps/demo/src/examples/* - Kanban demos:
apps/demo/src/examples/BasicKanban/index.tsxapps/demo/src/examples/RichKanban/index.tsxapps/demo/src/examples/SwimlanesKanban/index.tsxapps/demo/src/examples/WipLimitsKanban/index.tsx
High-level mapping for Kanban use cases:
DragDropContext->KanbanBoardDroppable->KanbanColumnViewDraggable->KanbanCardView
Important differences:
- State is normalized (
columns+cards) instead of nested. - IDs are explicit and owned by your app.
- Rendering is done via render functions (
renderColumn,renderCard).
In development, React Strict Mode can mount effects twice. Ensure listener setup and cleanup are idempotent when integrating custom logic.
Approximate minified sizes:
react-dragdrop-kit: about 5KBreact-dragdrop-kit/kanban: about 9KB
MIT