These four libraries provide solutions for implementing drag-and-drop (DnD) functionality in React applications, but they differ significantly in architecture, maintenance status, and flexibility. react-beautiful-dnd was the industry standard for list-based sorting but is now in maintenance mode. react-sortable-hoc is an older higher-order component (HOC) based solution that struggles with modern React patterns. react-dnd offers a low-level, highly customizable hook-based API suitable for complex, non-list interactions. @dnd-kit/core is the modern, modular successor designed for performance, accessibility, and extensibility using contemporary React patterns.
Building drag-and-drop interfaces in React ranges from simple list reordering to complex canvas manipulations. The choice of library dictates your architecture, performance ceiling, and accessibility compliance. Let's compare how @dnd-kit/core, react-beautiful-dnd, react-dnd, and react-sortable-hoc handle the core challenges of DnD development.
The fundamental building blocks differ wildly. Modern React favors hooks, while older libraries rely on Higher-Order Components (HOCs) or low-level state machines.
@dnd-kit/core uses a modular, hook-based architecture. It separates concerns into context providers and hooks, allowing you to compose behavior exactly where needed without wrapping your entire component tree in heavy HOCs.
// @dnd-kit: Composable hooks
import { DndContext, useDraggable } from '@dnd-kit/core';
function DraggableItem({ id }) {
const { attributes, listeners, setNodeRef, transform } = useDraggable({ id });
return (
<div ref={setNodeRef} style={{ transform }} {...listeners} {...attributes}>
Drag me
</div>
);
}
function App() {
return (
<DndContext onDragEnd={handleDragEnd}>
<DraggableItem id="item-1" />
</DndContext>
);
}
react-beautiful-dnd relies on a strict component hierarchy (DragDropContext, Droppable, Draggable). It abstracts most logic but forces you into its specific component structure.
// react-beautiful-dnd: Strict component hierarchy
import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd';
function DraggableItem({ item, index }) {
return (
<Draggable draggableId={item.id} index={index}>
{(provided) => (
<div ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps}>
{item.content}
</div>
)}
</Draggable>
);
}
function App() {
return (
<DragDropContext onDragEnd={onDragEnd}>
<Droppable droppableId="list">
{(provided) => (
<div ref={provided.innerRef} {...provided.droppableProps}>
{/* items */}
</div>
)}
</Droppable>
</DragDropContext>
);
}
react-dnd provides low-level hooks (useDrag, useDrop) that give you raw control over the drag state. It requires you to manually manage the connection between drag sources and drop targets.
// react-dnd: Low-level hooks
import { useDrag, useDrop } from 'react-dnd';
function DraggableItem({ id }) {
const [{ isDragging }, drag] = useDrag(() => ({
type: 'ITEM',
item: { id },
collect: (monitor) => ({ isDragging: monitor.isDragging() }),
}));
return <div ref={drag} style={{ opacity: isDragging ? 0.5 : 1 }}>Drag me</div>;
}
function DropZone() {
const [{ isOver }, drop] = useDrop(() => ({
accept: 'ITEM',
drop: (item) => console.log('Dropped', item),
collect: (monitor) => ({ isOver: monitor.isOver() }),
}));
return <div ref={drop}>Drop here</div>;
}
react-sortable-hoc uses Higher-Order Components (HOCs) to inject props. This pattern is largely deprecated in modern React as it complicates component composition and tree shaking.
// react-sortable-hoc: HOC pattern
import { sortableContainer, sortableElement } from 'react-sortable-hoc';
const SortableItem = sortableElement(({ value }) => <li>{value}</li>);
const SortableList = sortableContainer(({ items }) => (
<ul>
{items.map((value, index) => (
<SortableItem key={`item-${value}`} index={index} value={value} />
))}
</ul>
));
// Usage requires wrapping your component
function App() {
return <SortableList items={['Item 1', 'Item 2']} />;
}
Accessibility (a11y) is often an afterthought, but critical for production apps. Libraries handle keyboard navigation and screen reader support differently.
@dnd-kit/core ships with accessibility built-in. It automatically manages keyboard interactions (arrow keys to move, space to pick up) and ARIA attributes when you use its sensors and modifiers.
// @dnd-kit: Automatic keyboard support via sensors
import { DndContext, KeyboardSensor, PointerSensor, useSensor } from '@dnd-kit/core';
function App() {
const sensors = useSensor(PointerSensor, { activationConstraint: { distance: 5 } });
const keyboardSensor = useSensor(KeyboardSensor);
return (
<DndContext sensors={[sensors, keyboardSensor]} onDragEnd={handleDragEnd}>
{/* Keyboard navigation works out of the box */}
<DraggableItem id="a11y-item" />
</DndContext>
);
}
react-beautiful-dnd has excellent accessibility out of the box, strictly enforcing keyboard interaction patterns. However, customizing these behaviors is difficult due to its closed architecture.
// react-beautiful-dnd: Built-in but rigid a11y
// Users can tab to the item, press space to lift, arrow keys to move, space to drop.
// No code needed, but you cannot easily change the key bindings.
<Draggable draggableId="item">
{(provided) => (
<div ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps}>
Accessible by default
</div>
)}
</Draggable>
react-dnd provides zero accessibility features by default. You must implement keyboard handlers, focus management, and ARIA live regions manually, which is error-prone.
// react-dnd: Manual a11y implementation required
function DraggableItem({ id }) {
const [{ isDragging }, drag] = useDrag({ type: 'BOX', item: { id } });
// You must write this logic yourself
const handleKeyDown = (e) => {
if (e.key === 'Enter') { /* initiate drag */ }
if (e.key === 'ArrowRight') { /* move logic */ }
};
return (
<div
ref={drag}
tabIndex={0}
role="button"
aria-grabbed={isDragging}
onKeyDown={handleKeyDown}
>
Manual A11y
</div>
);
}
react-sortable-hoc includes basic keyboard support but suffers from the limitations of its HOC design, making it hard to extend for complex ARIA requirements in modern dynamic UIs.
// react-sortable-hoc: Basic keyboard support via HOC props
// The HOC injects keyboard listeners, but customizing roles or live regions
// requires fighting against the injected props.
const SortableItem = sortableElement(({ value, attributes }) => (
<li {...attributes}> {/* attributes contains basic a11y props */} {value} </li>
));
How much control do you have over the visual feedback and drag constraints?
@dnd-kit/core uses a "modifier" system. You can easily stack transforms, snap to grids, or restrict axes without rewriting the core logic. It renders a single drag overlay, giving you full control over the visual representation during drag.
// @dnd-kit: Modifiers for constraints
import { restrictToVerticalAxis, snapToGrid } from '@dnd-kit/modifiers';
function App() {
return (
<DndContext
modifiers={[restrictToVerticalAxis, snapToGrid({ x: 50, y: 50 })]}
onDragEnd={handleDragEnd}
>
<DraggableItem id="constrained" />
</DndContext>
);
}
react-beautiful-dnd is opinionated. It forces a specific "clone" animation style and makes it very hard to render a custom drag preview or deviate from its standard list animation.
// react-beautiful-dnd: Limited customization
// You cannot easily change the drag preview or animation curve without
// using unsupported hacks or CSS overrides that may break in updates.
<Draggable draggableId="item">
{(provided, snapshot) => (
<div
ref={provided.innerRef}
style={{
...provided.draggableProps.style,
transform: snapshot.isDragging ? 'rotate(5deg)' : 'none' // Limited inline styles
}}
>
Standard Animation
</div>
)}
</Draggable>
react-dnd offers maximum customization. You can render completely different components for the drag layer, use custom HTML5 drag previews, or even drive WebGL canvases.
// react-dnd: Full control over drag preview
import { useDragLayer } from 'react-dnd';
function CustomDragLayer() {
const { item, isDragging, currentOffset } = useDragLayer((monitor) => ({
item: monitor.getItem(),
isDragging: monitor.isDragging(),
currentOffset: monitor.getSourceClientOffset(),
}));
if (!isDragging) return null;
return (
<div style={{ transform: `translate(${currentOffset.x}px, ${currentOffset.y}px)` }}>
<CustomGraphic /> {/* Completely custom UI */}
</div>
);
}
react-sortable-hoc allows some customization via props but is constrained by the HOC's internal state management. Creating complex drag layers often requires breaking out of the HOC pattern entirely.
// react-sortable-hoc: Moderate customization
// You can pass `useDragHandle` to limit drag zones, but custom drag layers
// are difficult to synchronize with the HOC's internal transition logic.
const SortableItem = sortableElement(({ value, useDragHandle }) => (
<li>
<span {...useDragHandle}>::</span> {value}
</li>
));
A critical architectural decision is the long-term viability of the dependency.
react-beautiful-dnd is officially in maintenance mode. The maintainers have stated they will not add new features or support React 18's concurrent features fully. It is safe for existing apps but risky for new ones.
// react-beautiful-dnd: Maintenance Mode Warning
// Do not use for new projects. Known issues with React 18 Strict Mode
// and concurrent rendering may cause visual glitches.
import { DragDropContext } from 'react-beautiful-dnd'; // Legacy API
react-sortable-hoc is effectively deprecated for modern development. It has not seen significant updates in years and does not align with current React best practices (hooks, function components).
// react-sortable-hoc: Deprecated Pattern
// Relying on class components and HOCs makes migration to React Server Components
// or concurrent features difficult.
import { sortableContainer } from 'react-sortable-hoc'; // Outdated API
react-dnd is actively maintained but moves slowly. It prioritizes stability and backward compatibility. It is a safe bet for complex, long-term enterprise projects where requirements won't change drastically.
// react-dnd: Stable and Active
// Regular updates ensure compatibility with latest React versions,
// though the API surface remains large and complex.
import { DndProvider } from 'react-dnd';
@dnd-kit/core is actively developed and designed for the future of React. It embraces concurrent rendering, server components, and modular design. It is the recommended path forward.
// @dnd-kit: Modern and Active
// Built with React 18+ in mind, supporting concurrent features and
// offering a growing ecosystem of plugins (sortable, multi-tree, etc.).
import { DndContext } from '@dnd-kit/core';
You need a basic list where users can reorder tasks.
@dnd-kit/sortable (extension of core) or react-beautiful-dnd (if legacy).@dnd-kit provides a pre-built SortableContext that handles the array reordering logic with minimal code, while being future-proof.// @dnd-kit: Simple list with sortable extension
import { SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable';
<SortableContext items={items} strategy={verticalListSortingStrategy}>
{items.map((item) => <SortableItem key={item.id} item={item} />)}
</SortableContext>
You have multiple lists, and items can move between them, with custom cards.
@dnd-kit/core or react-dnd.react-beautiful-dnd struggles with multiple drop zones in complex layouts without performance hits. @dnd-kit handles multiple droppable containers efficiently.// react-dnd: Multiple drop zones
// You define a unique 'droppableId' for each column and handle the logic in onDrop.
<Column id="todo" accept="CARD" onDrop={handleDropToTodo} />
<Column id="done" accept="CARD" onDrop={handleDropToDone} />
Users drag nodes onto a free-form canvas, connect them, and pan/zoom.
react-dnd.@dnd-kit can do this but requires more custom modifiers; react-dnd is built for this level of freedom.// react-dnd: Free form canvas
// Use useDrag to track x/y coordinates directly without snapping to a list index.
const [{ }, drag] = useDrag(() => ({
type: 'NODE',
item: { x: startX, y: startY },
collect: (monitor) => ({ ... })
}));
Users rearrange widgets in a responsive grid.
@dnd-kit/core.react-sortable-hoc.// @dnd-kit: Grid constraints
<DndContext modifiers={[restrictToFirstScrollableAncestor]}>
<Grid>{widgets.map(w => <Widget key={w.id} {...w} />)}</Grid>
</DndContext>
| Feature | @dnd-kit/core | react-beautiful-dnd | react-dnd | react-sortable-hoc |
|---|---|---|---|---|
| Architecture | Hooks & Context | Component Tree | Low-level Hooks | Higher-Order Components |
| Maintenance | ✅ Active | ⚠️ Maintenance Mode | ✅ Active | ❌ Deprecated/Stale |
| Accessibility | ✅ Built-in & Extensible | ✅ Built-in (Rigid) | ❌ Manual Required | ⚠️ Basic |
| Customization | High (Modifiers) | Low (Opinionated) | Very High (Raw) | Medium |
| React 18 Ready | ✅ Yes | ⚠️ Partial/Issues | ✅ Yes | ❌ No |
| Best For | Modern Apps, Grids | Legacy Lists | Complex/Custom UIs | Legacy Class Components |
@dnd-kit/core is the modern standard 🚀. It balances ease of use with extreme flexibility. If you are starting a new project today, this should be your default choice. Its modular design means you only pay for what you use, and its accessibility story is unmatched.
react-dnd is the power tool 🔧. It has a steeper learning curve and requires more boilerplate, but it imposes no limits on what you can build. Choose it for non-standard interfaces like flowcharts, whiteboards, or complex data visualizations.
react-beautiful-dnd is the legacy champion 🏆. It made DnD easy for everyone, but its time has passed. Only use it if you are maintaining an existing app that relies on it; do not introduce it to new codebases.
react-sortable-hoc is the retired veteran 👴. Its HOC pattern is incompatible with the direction of React. Avoid it unless you are stuck maintaining very old code.
Final Thought: The landscape has shifted from "one library fits all" to specialized tools. For 90% of use cases, @dnd-kit offers the best balance of developer experience and performance. Reserve react-dnd for the 10% of cases where you need total control over the physics and rendering of the drag interaction.
Choose @dnd-kit/core for new projects requiring high performance, full accessibility support, and a modular architecture. It is the best fit if you need to build custom interactions beyond simple lists, such as 2D grids or complex dashboards, and want a library actively maintained with modern React hooks.
Choose react-beautiful-dnd only if you are maintaining an existing legacy codebase that already relies on it for simple vertical or horizontal lists. Do not start new projects with this library, as it is in maintenance mode and lacks support for modern React features like concurrent rendering and complex multi-container layouts.
Choose react-dnd if you need granular control over drag-and-drop logic for highly custom interfaces, such as flowcharts, infinite canvases, or complex node editors. It is ideal when you need to support multiple backends (HTML5, Touch, Mouse) simultaneously and are willing to write more boilerplate code for maximum flexibility.
Avoid react-sortable-hoc for new development. Its reliance on Higher-Order Components (HOCs) creates significant friction with modern React patterns like hooks and function components. It is generally only relevant for very old class-based projects that cannot be easily refactored.
@dnd-kit – a lightweight React library for building performant and accessible drag and drop experiences.
To get started, install the @dnd-kit/core package via npm or yarn:
npm install @dnd-kit/core
Visit docs.dndkit.com to learn how to get started with @dnd-kit.