This comparison evaluates five prominent libraries for implementing drag and drop functionality in React. react-beautiful-dnd focuses on accessible list reordering but is in maintenance mode. react-dnd offers maximum flexibility with a steep learning curve using HTML5 backend. react-draggable handles simple single-element dragging without list support. react-sortable-hoc uses higher-order components for sorting, representing an older React pattern. sortablejs is a vanilla JavaScript library that manipulates the DOM directly and requires careful integration with React.
Implementing drag and drop in React involves balancing user experience, performance, and code maintainability. The five libraries reviewed here — react-beautiful-dnd, react-dnd, react-draggable, react-sortable-hoc, and sortablejs — take different approaches to solving this problem. Some focus on lists, others on free positioning, and some on raw DOM control. Let's compare how they handle setup, element definition, and state management.
react-dnd requires a top-level provider to inject drag and drop context into your component tree.
DndProvider and select a backend (like HTML5).// react-dnd: Top-level provider
import { DndProvider } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
function App() {
return (
<DndProvider backend={HTML5Backend}>
<MyComponent />
</DndProvider>
);
}
react-beautiful-dnd also uses a context provider but is more opinionated about structure.
DragDropContext.// react-beautiful-dnd: Context wrapper
import { DragDropContext } from 'react-beautiful-dnd';
function App() {
return (
<DragDropContext onDragEnd={handleDragEnd}>
<MyList />
</DragDropContext>
);
}
react-sortable-hoc does not use a provider; it wraps specific components instead.
// react-sortable-hoc: HOC wrapper
import { sortableContainer } from 'react-sortable-hoc';
const SortableList = sortableContainer(({ children }) => <ul>{children}</ul>);
function App() {
return <SortableList items={items} />
}
react-draggable requires no provider or global context.
// react-draggable: No provider needed
import Draggable from 'react-draggable';
function App() {
return <Draggable><div className="box">Drag me</div></Draggable>;
}
sortablejs is vanilla JavaScript and initializes directly on DOM nodes.
ref to access the element and create a new instance.// sortablejs: Direct DOM initialization
import Sortable from 'sortablejs';
function MyList() {
const ref = useRef(null);
useEffect(() => {
new Sortable(ref.current, { animation: 150 });
}, []);
return <ul ref={ref}>...</ul>;
}
react-dnd uses hooks to declare drag sources and drop targets within components.
useDrag defines what is being dragged.useDrop defines where it can be dropped.// react-dnd: Hooks for drag and drop
function Box() {
const [{ isDragging }, drag] = useDrag(() => ({ type: 'BOX', collect: (m) => ({ isDragging: m.isDragging() }) }));
return <div ref={drag} style={{ opacity: isDragging ? 0.5 : 1 }}>Box</div>;
}
react-beautiful-dnd uses a specific component structure for every item.
<Draggable>.provided props to connect the DOM node.// react-beautiful-dnd: Draggable component
function Item({ item, index }) {
return (
<Draggable draggableId={item.id} index={index}>
{(provided) => (
<div ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps}>
{item.content}
</div>
)}
</Draggable>
);
}
react-sortable-hoc wraps items in a Higher-Order Component.
SortableElement to make items sortable.SortableHandle if you want a specific drag handle.// react-sortable-hoc: Sortable Element
const SortableItem = sortableElement(({ value }) => <li>{value}</li>);
function MyList({ items }) {
return (
<ul>{items.map((value, index) => (
<SortableItem key={`item-${value}`} index={index} value={value} />
))}</ul>
);
}
react-draggable wraps the single element directly.
// react-draggable: Simple wrapper
function Widget() {
return (
<Draggable axis="x" bounds="parent">
<div className="widget">Widget</div>
</Draggable>
);
}
sortablejs relies on CSS selectors or refs to identify items.
selector option to identify draggable nodes.sortablejs attaches events to the children.// sortablejs: Selector configuration
new Sortable(ref.current, {
animation: 150,
draggable: '.draggable-item' // CSS selector for items
});
// Render
<ul ref={ref}>
{items.map(item => <li key={item.id} className="draggable-item">{item.name}</li>)}
</ul>
react-dnd does not manage list state for you.
onDrop logic to update your state.// react-dnd: Manual state update
function Board() {
const [{ isOver }, drop] = useDrop(() => ({
accept: 'BOX',
drop: (item) => moveItem(item.id),
collect: (m) => ({ isOver: m.isOver() })
}));
return <div ref={drop}>Drop Zone</div>;
}
react-beautiful-dnd provides a structured onDragEnd event.
// react-beautiful-dnd: onDragEnd handler
function onDragEnd(result) {
if (!result.destination) return;
const items = reorder(list, result.source.index, result.destination.index);
setList(items);
}
react-sortable-hoc calls an onSortEnd callback with old and new indices.
react-beautiful-dnd but tied to the HOC lifecycle.// react-sortable-hoc: onSortEnd callback
function onSortEnd({ oldIndex, newIndex }) {
setItems(arrayMove(items, oldIndex, newIndex));
}
react-draggable focuses on position, not list order.
onStop with x/y coordinates.// react-draggable: Position handling
function onDragStop(e, data) {
console.log(`X: ${data.x}, Y: ${data.y}`);
savePosition(data.x, data.y);
}
sortablejs can update the DOM directly, which risks conflicting with React.
onChange or onEnd to trigger React re-renders.// sortablejs: Syncing with React state
new Sortable(ref.current, {
onEnd: (evt) => {
const { oldIndex, newIndex } = evt;
setItems(arrayMove(items, oldIndex, newIndex));
}
});
react-beautiful-dnd is officially in maintenance mode by Atlassian.
@hello-pangea/dnd is recommended for active development.react-dnd remains actively maintained and widely used.
react-draggable is stable and maintained for its specific use case.
react-sortable-hoc uses an older React pattern (HOCs).
sortablejs is a standalone library with a long history.
While the implementations differ, these libraries share common goals and patterns.
react-dnd requires a specific touch backend.// react-dnd: Touch backend
import { TouchBackend } from 'react-dnd-touch-backend';
<DndProvider backend={TouchBackend}>
// sortablejs: Native touch support
new Sortable(el, { fallbackOnBody: true });
react-dnd and react-beautiful-dnd offer strong APIs for this.// react-dnd: Custom preview
useDrag(() => ({
item: { type: 'BOX' },
collect: (monitor) => ({ isDragging: monitor.isDragging() })
}));
// react-beautiful-dnd: Custom preview via children
<Draggable>{(provided) => (<div ref={provided.innerRef}>{/* Custom UI */}</div>)}</Draggable>
array-move to update state.// Common pattern across RBD, RSH, SortableJS
const reorder = (list, startIndex, endIndex) => {
const result = Array.from(list);
const [removed] = result.splice(startIndex, 1);
result.splice(endIndex, 0, removed);
return result;
};
react-beautiful-dnd leads in this area out of the box.// react-beautiful-dnd: Keyboard support built-in
// Users can press space to pick up, arrows to move, space to drop
// react-dnd: Requires custom implementation for keyboard
// No default keyboard support in HTML5 backend
| Feature | react-dnd | react-beautiful-dnd | react-draggable |
|---|---|---|---|
| Primary Use | Complex grids, custom logic | Vertical lists | Single elements |
| Setup | Provider + Hooks | Context + Components | Wrapper Component |
| State Mgmt | Manual | Manual (Structured) | Manual (Position) |
| Status | Active | Maintenance Mode | Stable |
| Feature | react-sortable-hoc | sortablejs |
|---|---|---|
| Primary Use | Simple lists (Legacy) | High performance lists |
| Setup | Higher-Order Components | Vanilla JS Init |
| State Mgmt | Manual (Callbacks) | Manual (Sync Required) |
| Status | Legacy Pattern | Stable |
react-dnd is the power tool 🔨 — choose it for complex applications where drag and drop is core to the user experience. It handles edge cases and custom designs better than any other option.
react-beautiful-dnd (or its fork @hello-pangea/dnd) is the standard for lists 📋 — if you need a polished, accessible list view quickly, this is the path of least resistance. Just be aware of the maintenance status.
react-draggable is the specialist 🎯 — use it for draggable windows, icons, or map markers. Do not try to force it into list reordering.
react-sortable-hoc is the legacy choice 🕰️ — only use if maintaining older code. New projects should avoid the HOC pattern.
sortablejs is the performance engine 🏎️ — ideal for very long lists or when you need to share logic between React and non-React views. Requires careful state synchronization.
Final Thought: For most new React projects requiring list sorting, start with @hello-pangea/dnd (the fork of react-beautiful-dnd) for simplicity, or react-dnd if you anticipate complex requirements. Avoid mixing DOM manipulation libraries like sortablejs unless you have a specific performance need.
Choose react-draggable when you need to make individual elements draggable without list reordering logic. It is ideal for features like draggable windows, icons, or map markers where position matters more than order. The API is simple and requires minimal setup compared to full DnD frameworks. Avoid this if you need to sort lists or handle complex drop zones.
Choose react-dnd if you need full control over drag and drop behavior, such as custom drag layers, complex grids, or multiple drop targets. It supports swappable backends (HTML5, Touch, Mouse) which is critical for cross-device support. Be prepared for a steeper learning curve and more boilerplate code compared to opinionated libraries. It is the best fit for highly interactive applications like dashboards or design tools.
Choose sortablejs if you want a lightweight, framework-agnostic solution that works outside of React as well. It is highly performant for large lists and supports complex nested structures. You must manage React state synchronization manually to avoid conflicts between DOM manipulation and React renders. It is best when you need maximum performance or are integrating with non-React parts of an application.
Choose react-beautiful-dnd only for maintaining legacy projects that already depend on it, as it is officially in maintenance mode. It provides excellent accessibility and smooth animations for vertical lists out of the box. For new projects, consider its community fork @hello-pangea/dnd or switch to more actively maintained alternatives. It is not suitable for complex grids or non-list layouts.
Choose react-sortable-hoc if you are working on an older codebase that already uses Higher-Order Components and need simple list sorting. It handles touch events well and is easier to set up than react-dnd for basic lists. However, the HOC pattern is less favored in modern React development compared to hooks. New projects should generally avoid this in favor of hook-based solutions.
A simple component for making elements draggable.
<Draggable>
<div>I can now be moved around!</div>
</Draggable>
npm install react-draggable
# or
yarn add react-draggable
// ES Modules
import Draggable from 'react-draggable';
import { DraggableCore } from 'react-draggable';
// CommonJS
const Draggable = require('react-draggable');
const { DraggableCore } = require('react-draggable');
TypeScript types are included.
| Version | React Version |
|---|---|
| 4.x | 16.3+ |
| 3.x | 15 - 16 |
| 2.x | 0.14 - 15 |
import React, { useRef } from 'react';
import Draggable from 'react-draggable';
function App() {
const nodeRef = useRef(null);
return (
<Draggable nodeRef={nodeRef}>
<div ref={nodeRef}>Drag me!</div>
</Draggable>
);
}
View the Demo and its source for more examples.
<Draggable>A <Draggable> element wraps an existing element and extends it with new event handlers and styles. It does not create a wrapper element in the DOM.
Draggable items are moved using CSS Transforms. This allows items to be dragged regardless of their current positioning (relative, absolute, or static). Elements can also be moved between drags without incident.
If the item you are dragging already has a CSS Transform applied, it will be overwritten by <Draggable>. Use an intermediate wrapper (<Draggable><span>...</span></Draggable>) in this case.
type DraggableEventHandler = (e: Event, data: DraggableData) => void | false;
type DraggableData = {
node: HTMLElement,
x: number, y: number,
deltaX: number, deltaY: number,
lastX: number, lastY: number,
};
| Prop | Type | Default | Description |
|---|---|---|---|
allowAnyClick | boolean | false | Allow dragging on non-left-button clicks |
allowMobileScroll | boolean | false | Don't prevent touchstart, allowing scrolling inside containers |
axis | 'both' | 'x' | 'y' | 'none' | 'both' | Axis to allow dragging on |
bounds | object | string | - | Restrict movement. Use 'parent', a CSS selector, or {left, top, right, bottom} |
cancel | string | - | CSS selector for elements that should not initiate drag |
defaultClassName | string | 'react-draggable' | Class name applied to the element |
defaultClassNameDragging | string | 'react-draggable-dragging' | Class name applied while dragging |
defaultClassNameDragged | string | 'react-draggable-dragged' | Class name applied after drag |
defaultPosition | {x: number, y: number} | {x: 0, y: 0} | Starting position |
disabled | boolean | false | Disable dragging |
enableUserSelectHack | boolean | true | Add user-select: none while dragging |
grid | [number, number] | - | Snap to grid [x, y] |
handle | string | - | CSS selector for the drag handle |
nodeRef | React.RefObject | - | Ref to the DOM element. Required for React Strict Mode |
nonce | string | - | CSP nonce for the injected user-select <style> element (see Content Security Policy) |
offsetParent | HTMLElement | - | Custom offsetParent for drag calculations |
onDrag | DraggableEventHandler | - | Called while dragging |
onMouseDown | (e: MouseEvent) => void | - | Called on mouse down |
onStart | DraggableEventHandler | - | Called when dragging starts. Return false to cancel |
onStop | DraggableEventHandler | - | Called when dragging stops |
position | {x: number, y: number} | - | Controlled position |
positionOffset | {x: number | string, y: number | string} | - | Position offset (supports percentages) |
scale | number | 1 | Scale factor for dragging inside transformed parents |
Note: Setting className, style, or transform on <Draggable> will error. Set them on the child element.
<DraggableCore>For users that require full control, <DraggableCore> provides drag callbacks without managing state or styles. It does not set any transforms; you must handle positioning yourself.
See React-Resizable and React-Grid-Layout for usage examples.
<DraggableCore> accepts a subset of <Draggable> props:
allowAnyClickallowMobileScrollcanceldisabledenableUserSelectHackgridhandlenodeRefoffsetParentonDragonMouseDownonStartonStopscaleTo avoid ReactDOM.findDOMNode() deprecation warnings in React Strict Mode, pass a nodeRef prop:
function App() {
const nodeRef = useRef(null);
return (
<Draggable nodeRef={nodeRef}>
<div ref={nodeRef}>Drag me!</div>
</Draggable>
);
}
For custom components, forward both the ref and props:
const MyComponent = forwardRef((props, ref) => (
<div {...props} ref={ref}>Draggable content</div>
));
function App() {
const nodeRef = useRef(null);
return (
<Draggable nodeRef={nodeRef}>
<MyComponent ref={nodeRef} />
</Draggable>
);
}
<Draggable> is a 'batteries-included' component that manages its own state. For complete control, use <DraggableCore>.
For programmatic repositioning while using <Draggable>'s state management, pass the position prop:
function ControlledDraggable() {
const nodeRef = useRef(null);
const [position, setPosition] = useState({ x: 0, y: 0 });
const handleDrag = (e, data) => {
setPosition({ x: data.x, y: data.y });
};
const resetPosition = () => setPosition({ x: 0, y: 0 });
return (
<>
<button onClick={resetPosition}>Reset</button>
<Draggable nodeRef={nodeRef} position={position} onDrag={handleDrag}>
<div ref={nodeRef}>Drag me or reset!</div>
</Draggable>
</>
);
}
To prevent text from being highlighted while dragging, react-draggable injects a
small <style> element into the document <head> the first time a drag starts
(the enableUserSelectHack, on by default). Under a strict Content Security
Policy that omits 'unsafe-inline' from style-src, the browser blocks that
element and logs a CSP violation.
You have three ways to handle this:
Pass a nonce. Provide the same nonce your CSP header advertises and it's
applied to the injected element:
<Draggable nonce={cspNonce}>
<div>Drag me</div>
</Draggable>
Do nothing, if you use webpack. When no nonce prop is given,
react-draggable falls back to webpack's
__webpack_nonce__ global if your build
defines it — no per-component prop needed.
Opt out of the injected style. Set enableUserSelectHack={false} and add
the two rules to your own (CSP-compliant) stylesheet:
.react-draggable-transparent-selection *::-moz-selection { all: inherit; }
.react-draggable-transparent-selection *::selection { all: inherit; }
The nonce is only read when the element is first created. The same element is
shared by every <Draggable>/<DraggableCore> on the page, so set the nonce on
whichever instance drags first (or, more simply, set it consistently everywhere).
yarn dev to start the development serveryarn test to ensure tests passmake release-patch, make release-minor, or make release-majormake publishMIT