react-draggable vs react-dnd vs sortablejs vs react-beautiful-dnd vs react-sortable-hoc
Drag and Drop Libraries for React Applications
react-draggablereact-dndsortablejsreact-beautiful-dndreact-sortable-hoc

Drag and Drop Libraries for React Applications

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.

Npm Package Weekly Downloads Trend

3 Years

Github Stars Ranking

Stat Detail

Package
Downloads
Stars
Size
Issues
Publish
License
react-draggable5,453,4149,287453 kB2088 days agoMIT
react-dnd3,927,88921,627231 kB474-MIT
sortablejs3,037,41531,183640 kB5277 months agoMIT
react-beautiful-dnd1,610,53633,9311.39 MB642-Apache-2.0
react-sortable-hoc461,83810,886-2896 years agoMIT

Drag and Drop Libraries for React: Architecture and Implementation Compared

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.

🏗️ Setup and Configuration: Providers vs Direct Initialization

react-dnd requires a top-level provider to inject drag and drop context into your component tree.

  • You must wrap your app with DndProvider and select a backend (like HTML5).
  • This allows deep customization but adds initial boilerplate.
// 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.

  • Wrap your list with DragDropContext.
  • It manages accessibility and keyboard support automatically.
// 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.

  • You apply a Higher-Order Component to the list container.
  • Less global setup, but ties logic closely to the component definition.
// 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.

  • You simply wrap the element you want to be draggable.
  • Ideal for isolated interactions without app-wide state.
// 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.

  • You use a ref to access the element and create a new instance.
  • No React context is involved, which reduces overhead but increases manual wiring.
// 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>;
}

🧩 Defining Draggable Elements: Components vs Refs

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.
  • Gives you full control over the drag preview and cursor.
// 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.

  • Each item must be wrapped in <Draggable>.
  • You must pass 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.

  • Use SortableElement to make items sortable.
  • Use 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.

  • No special props needed inside the child.
  • You can restrict axes or bounds easily.
// 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.

  • You configure the selector option to identify draggable nodes.
  • React renders the list, 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>

🔄 Handling State and Reordering: Internal vs External

react-dnd does not manage list state for you.

  • You must write the onDrop logic to update your state.
  • This offers flexibility but requires more code for simple lists.
// 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.

  • You receive source and destination indices.
  • You must update state manually, but the data shape is consistent.
// 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.

  • Similar to react-beautiful-dnd but tied to the HOC lifecycle.
  • You still need to update your own state array.
// react-sortable-hoc: onSortEnd callback
function onSortEnd({ oldIndex, newIndex }) {
  setItems(arrayMove(items, oldIndex, newIndex));
}

react-draggable focuses on position, not list order.

  • It provides onStop with x/y coordinates.
  • You save position data rather than array indices.
// 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.

  • Best practice is to disable internal sorting and use callbacks to update React state.
  • Use 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));
  }
});

⚠️ Maintenance and Ecosystem Status

react-beautiful-dnd is officially in maintenance mode by Atlassian.

  • No new features are planned, only critical bug fixes.
  • The community fork @hello-pangea/dnd is recommended for active development.

react-dnd remains actively maintained and widely used.

  • It adapts well to new React versions and patterns.
  • Best choice for long-term, complex projects.

react-draggable is stable and maintained for its specific use case.

  • It is not expanding scope, which keeps it reliable.
  • Safe to use for draggable windows or icons.

react-sortable-hoc uses an older React pattern (HOCs).

  • Development has slowed significantly.
  • New projects should prefer hook-based alternatives.

sortablejs is a standalone library with a long history.

  • It is very stable and independent of React releases.
  • Great for performance-critical lists where React overhead is a concern.

🤝 Similarities: Shared Ground Between Libraries

While the implementations differ, these libraries share common goals and patterns.

1. 🖱️ Mouse and Touch Support

  • All libraries support mouse interactions by default.
  • Most support touch, though 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 });

2. 🎨 Custom Drag Previews

  • You can customize what the user sees while dragging.
  • 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>

3. 📋 List Reordering Logic

  • Most provide indices to calculate new order.
  • You typically use a helper like 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;
};

4. ✅ Accessibility Considerations

  • Keyboard support varies significantly between libraries.
  • 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

📊 Summary: Key Differences

Featurereact-dndreact-beautiful-dndreact-draggable
Primary UseComplex grids, custom logicVertical listsSingle elements
SetupProvider + HooksContext + ComponentsWrapper Component
State MgmtManualManual (Structured)Manual (Position)
StatusActiveMaintenance ModeStable
Featurereact-sortable-hocsortablejs
Primary UseSimple lists (Legacy)High performance lists
SetupHigher-Order ComponentsVanilla JS Init
State MgmtManual (Callbacks)Manual (Sync Required)
StatusLegacy PatternStable

💡 The Big Picture

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.

How to Choose: react-draggable vs react-dnd vs sortablejs vs react-beautiful-dnd vs react-sortable-hoc

  • react-draggable:

    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.

  • react-dnd:

    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.

  • sortablejs:

    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.

  • react-beautiful-dnd:

    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.

  • react-sortable-hoc:

    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.

README for react-draggable

React-Draggable

CI npm version npm downloads gzip size

A simple component for making elements draggable.

[Demo | Changelog]

<Draggable>
  <div>I can now be moved around!</div>
</Draggable>

Table of Contents

Installation

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.

Compatibility

VersionReact Version
4.x16.3+
3.x15 - 16
2.x0.14 - 15

Quick Start

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.

API

<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.

Props

type DraggableEventHandler = (e: Event, data: DraggableData) => void | false;

type DraggableData = {
  node: HTMLElement,
  x: number, y: number,
  deltaX: number, deltaY: number,
  lastX: number, lastY: number,
};
PropTypeDefaultDescription
allowAnyClickbooleanfalseAllow dragging on non-left-button clicks
allowMobileScrollbooleanfalseDon't prevent touchstart, allowing scrolling inside containers
axis'both' | 'x' | 'y' | 'none''both'Axis to allow dragging on
boundsobject | string-Restrict movement. Use 'parent', a CSS selector, or {left, top, right, bottom}
cancelstring-CSS selector for elements that should not initiate drag
defaultClassNamestring'react-draggable'Class name applied to the element
defaultClassNameDraggingstring'react-draggable-dragging'Class name applied while dragging
defaultClassNameDraggedstring'react-draggable-dragged'Class name applied after drag
defaultPosition{x: number, y: number}{x: 0, y: 0}Starting position
disabledbooleanfalseDisable dragging
enableUserSelectHackbooleantrueAdd user-select: none while dragging
grid[number, number]-Snap to grid [x, y]
handlestring-CSS selector for the drag handle
nodeRefReact.RefObject-Ref to the DOM element. Required for React Strict Mode
noncestring-CSP nonce for the injected user-select <style> element (see Content Security Policy)
offsetParentHTMLElement-Custom offsetParent for drag calculations
onDragDraggableEventHandler-Called while dragging
onMouseDown(e: MouseEvent) => void-Called on mouse down
onStartDraggableEventHandler-Called when dragging starts. Return false to cancel
onStopDraggableEventHandler-Called when dragging stops
position{x: number, y: number}-Controlled position
positionOffset{x: number | string, y: number | string}-Position offset (supports percentages)
scalenumber1Scale 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.

Props

<DraggableCore> accepts a subset of <Draggable> props:

  • allowAnyClick
  • allowMobileScroll
  • cancel
  • disabled
  • enableUserSelectHack
  • grid
  • handle
  • nodeRef
  • offsetParent
  • onDrag
  • onMouseDown
  • onStart
  • onStop
  • scale

Using nodeRef

To 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>
  );
}

Controlled vs. Uncontrolled

<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>
    </>
  );
}

Content Security Policy

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:

  1. 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>
    
  2. 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.

  3. 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).

Contributing

  • Fork the project
  • Run yarn dev to start the development server
  • Make changes and add tests
  • Run yarn test to ensure tests pass
  • Submit a PR

Release Checklist

  1. Update CHANGELOG.md
  2. Run make release-patch, make release-minor, or make release-major
  3. Run make publish

License

MIT