react-draggable vs react-grid-layout vs react-resizable
Building Interactive Drag-and-Drop and Resizable Layouts in React
react-draggablereact-grid-layoutreact-resizableSimilar Packages:

Building Interactive Drag-and-Drop and Resizable Layouts in React

react-draggable, react-grid-layout, and react-resizable are foundational utilities for building interactive user interfaces in React. react-draggable provides a simple wrapper to make any single component draggable within a container. react-grid-layout is a more complex, grid-based system designed for creating dashboard-like layouts where multiple items can be dragged, resized, and arranged in a responsive grid. react-resizable focuses specifically on adding resize handles to components, often used in conjunction with react-draggable or react-grid-layout to create fully interactive panels. Together, they cover the spectrum from simple drag interactions to complex, persistent dashboard architectures.

Npm Package Weekly Downloads Trend

3 Years

Github Stars Ranking

Stat Detail

Package
Downloads
Stars
Size
Issues
Publish
License
react-draggable09,293443 kB224a month agoMIT
react-grid-layout022,349446 kB954 months agoMIT
react-resizable02,57944.3 kB9a month agoMIT

React Draggable vs Grid Layout vs Resizable: Choosing the Right Interaction Model

When building interactive React applications, you often need to let users move things around or change their size. While these actions seem similar, the underlying logic for a single draggable item, a full dashboard grid, and a resizable pane differs significantly. react-draggable, react-grid-layout, and react-resizable solve these specific problems. Let's look at how they handle real-world engineering challenges.

๐Ÿ–ฑ๏ธ Basic Dragging: Free Form vs. Grid Constrained

react-draggable lets you move an element anywhere within its parent container. It does not care about other elements or a grid. You just wrap a component, and it becomes movable.

import Draggable from 'react-draggable';

function FreeMovingBox() {
  return (
    <Draggable>
      <div className="box">I can go anywhere</div>
    </Draggable>
  );
}

react-grid-layout forces elements into a strict grid. You cannot place an item at arbitrary pixel coordinates; you must define its position using grid units (columns and rows). It automatically prevents items from overlapping.

import ReactGridLayout from 'react-grid-layout';

function DashboardGrid() {
  const layout = [
    { i: 'a', x: 0, y: 0, w: 2, h: 2 },
    { i: 'b', x: 2, y: 0, w: 2, h: 2 }
  ];

  return (
    <ReactGridLayout className="layout" cols={12} rowHeight={30} width={1200}>
      <div key="a">Item A (Grid Snapped)</div>
      <div key="b">Item B (Grid Snapped)</div>
    </ReactGridLayout>
  );
}

react-resizable does not handle dragging position by itself. It only handles changing the size. If you try to use it for moving items, you will find it lacks the onDrag logic needed to update top or left CSS properties.

import { Resizable } from 'react-resizable';

function ResizableBox() {
  return (
    <Resizable width={200} height={200} handleSize={[10, 10]}>
      <div className="box">I can only resize, not move</div>
    </Resizable>
  );
}

๐Ÿ“ Resizing Logic: Standalone vs. Integrated

react-resizable gives you fine-grained control over resize handles. You can decide exactly where the handles appear (north, south, east, west, corners) and how they behave. It is perfect for split-panes or image editors.

import { Resizable } from 'react-resizable';

function CustomPane() {
  const onResize = (e, { size }) => {
    console.log(`New size: ${size.width}x${size.height}`);
  };

  return (
    <Resizable 
      width={300} 
      height={300} 
      onResize={onResize}
      handle={<span className="custom-handle" />}
    >
      <div className="content">Resize me with custom handles</div>
    </Resizable>
  );
}

react-grid-layout has resizing built-in. You do not need to install react-resizable separately if you are already using the grid. The resizing is constrained by the grid columns and rows, ensuring the layout stays clean.

import ReactGridLayout from 'react-grid-layout';

function GridWithResize() {
  // Items are resizable by default if 'isResizable' is true (default)
  const layout = [{ i: 'a', x: 0, y: 0, w: 2, h: 2 }];

  return (
    <ReactGridLayout 
      className="layout" 
      cols={12} 
      rowHeight={30} 
      width={1200}
      onLayoutChange={(newLayout) => console.log(newLayout)}
    >
      <div key="a">Drag or Resize me within the grid</div>
    </ReactGridLayout>
  );
}

react-draggable has no built-in resizing. To make a draggable item also resizable, you must nest a react-resizable component inside the Draggable component. This requires careful state management to ensure the drag logic doesn't conflict with the resize logic.

import Draggable from 'react-draggable';
import { Resizable } from 'react-resizable';

function DragAndResize() {
  return (
    <Draggable>
      <Resizable width={200} height={200}>
        <div className="box">Drag the box, resize the inner handle</div>
      </Resizable>
    </Draggable>
  );
}

๐Ÿงฉ Layout Management: Collision and Persistence

react-grid-layout excels at managing complex layouts. If you move an item into another, it automatically pushes the other items out of the way (collision detection). It also supports saving the layout state to a database and restoring it later.

import ReactGridLayout from 'react-grid-layout';

function PersistentDashboard() {
  const [layout, setLayout] = React.useState(initialLayoutFromDB);

  const onLayoutChange = (newLayout) => {
    // Save newLayout to your backend here
    setLayout(newLayout);
  };

  return (
    <ReactGridLayout 
      layout={layout} 
      onLayoutChange={onLayoutChange}
      cols={12}
    >
      {/* Items automatically rearrange to avoid overlap */}
      <div key="1">Widget 1</div>
      <div key="2">Widget 2</div>
    </ReactGridLayout>
  );
}

react-draggable does not handle collisions. If you drag one item over another, they will simply overlap. You must write your own logic if you want items to push each other or snap into slots.

import Draggable from 'react-draggable';

function OverlappingItems() {
  return (
    <div style={{ position: 'relative', height: '400px' }}>
      <Draggable defaultPosition={{ x: 0, y: 0 }}>
        <div className="box">Item 1</div>
      </Draggable>
      <Draggable defaultPosition={{ x: 20, y: 20 }}>
        <div className="box">Item 2 (Will overlap Item 1)</div>
      </Draggable>
    </div>
  );
}

react-resizable is unaware of other elements. It only knows about its own width and height. It offers no help with layout persistence or collision detection.

import { Resizable } from 'react-resizable';

function IndependentResize() {
  return (
    <div>
      <Resizable width={100} height={100}>
        <div>Box A</div>
      </Resizable>
      <Resizable width={100} height={100}>
        <div>Box B (No awareness of Box A)</div>
      </Resizable>
    </div>
  );
}

๐Ÿ“ฑ Responsive Behavior

react-grid-layout has first-class support for responsive designs. You can define different layouts for different screen widths (breakpoints). When the screen shrinks, the grid automatically reflows items.

import ReactGridLayout from 'react-grid-layout';

function ResponsiveGrid() {
  const layouts = {
    lg: [{ i: 'a', x: 0, y: 0, w: 4, h: 4 }],
    md: [{ i: 'a', x: 0, y: 0, w: 6, h: 4 }],
    sm: [{ i: 'a', x: 0, y: 0, w: 12, h: 4 }]
  };

  return (
    <ReactGridLayout 
      layouts={layouts} 
      breakpoints={{ lg: 1200, md: 996, sm: 768 }}
      cols={{ lg: 12, md: 10, sm: 6 }}
    >
      <div key="a">Reflows automatically</div>
    </ReactGridLayout>
  );
}

react-draggable and react-resizable do not have built-in responsive breakpoints. If you need responsive behavior, you must listen to window resize events and manually update the position or size props, which adds significant complexity.

import Draggable from 'react-draggable';

function ManualResponsive() {
  const [width, setWidth] = React.useState(window.innerWidth);

  React.useEffect(() => {
    const handleResize = () => setWidth(window.innerWidth);
    window.addEventListener('resize', handleResize);
    return () => window.removeEventListener('resize', handleResize);
  }, []);

  return (
    <Draggable bounds="parent">
      <div style={{ width: width < 600 ? '100%' : '300px' }}>
        Manual responsive logic required
      </div>
    </Draggable>
  );
}

๐Ÿค Similarities: Shared Foundations

Despite their different goals, these libraries share common patterns and dependencies.

1. ๐Ÿงฑ DOM Manipulation via Transforms

All three libraries primarily use CSS transform properties (translate for drag, scale or direct width/height for resize) to perform animations. This ensures high performance (60fps) by avoiding expensive layout recalculations.

// All libraries result in inline styles similar to this:
<div style={{ transform: 'translate3d(10px, 20px, 0)' }}>Content</div>

2. ๐Ÿ–๏ธ Event Handling

They all attach mouse and touch event listeners to handle user interaction. They normalize events to ensure dragging works on both desktop mice and mobile touchscreens.

// Internal logic in all three handles:
// onMouseDown / onTouchStart -> track start position
// onMouseMove / onTouchMove -> calculate delta
// onMouseUp / onTouchEnd -> finalize position

3. โš›๏ธ React Controlled vs. Uncontrolled

All three support both controlled (state driven) and uncontrolled (internal state) modes. This allows you to choose between simple setup or full integration with your app's state management.

// Uncontrolled (Simple)
<Draggable defaultPosition={{ x: 0, y: 0 }} />

// Controlled (Full sync with state)
<Draggable position={position} onStop={(e, data) => setPosition(data)} />

๐Ÿ“Š Summary: Key Differences

Featurereact-draggablereact-grid-layoutreact-resizable
Primary GoalMove single items freelyManage complex grid dashboardsChange component dimensions
Collision DetectionโŒ No (Overlap allowed)โœ… Yes (Auto-push items)โŒ No
Grid SnappingโŒ Noโœ… YesโŒ No
ResizingโŒ No (Needs nesting)โœ… Built-inโœ… Core Feature
Responsive BreakpointsโŒ Manualโœ… Built-inโŒ Manual
Best ForModals, Lists, GamesAdmin Panels, DashboardsSplit-panes, Image Editors

๐Ÿ’ก The Big Picture

react-draggable is your go-to for simple, free-form movement. Use it when you don't need a grid and just want to let users pick up and move an element. It is lightweight and easy to drop into any project.

react-grid-layout is a complete layout engine. If you are building a dashboard where users expect to rearrange widgets, save their layout, and have it work on mobile, this is the only serious choice. It solves the hard problems of collision and responsiveness for you.

react-resizable is a specialized tool for resizing. Use it when you need custom resize handles on specific elements. However, if you are already using react-grid-layout, you likely don't need this package, as the grid system already handles resizing within its constraints.

Final Thought: Start with react-draggable for simple interactions. If your requirements grow to include multiple items that must not overlap and need to fit a grid, migrate to react-grid-layout. Only reach for react-resizable if you need custom resizing logic outside of a grid system.

How to Choose: react-draggable vs react-grid-layout vs react-resizable

  • react-draggable:

    Choose react-draggable when you need to make a single element or a small list of independent items draggable without a strict grid structure. It is ideal for scenarios like moving a modal, reordering a simple list, or creating a custom drag-and-drop game mechanic where you need full control over the coordinates. Avoid it if you need automatic collision detection, grid snapping, or complex layout management, as it only handles the translation of the element.

  • react-grid-layout:

    Choose react-grid-layout if you are building a dashboard, admin panel, or any interface where users need to arrange multiple widgets in a responsive grid. It is the best fit when you require features like automatic packing, prevention of item overlap, persistent layout saving, and distinct breakpoints for mobile vs. desktop. Do not use it for simple, free-form dragging outside of a grid context, as its overhead and strict grid constraints will be unnecessary and restrictive.

  • react-resizable:

    Choose react-resizable when your primary requirement is to allow users to change the dimensions of a component, such as a sidebar, a split-pane view, or a resizable image crop box. It is often used alongside react-draggable to create components that can both move and resize. If you are already using react-grid-layout, note that it includes resizing capabilities built-in, so adding this package separately is usually redundant unless you need its specific handle customization outside of the grid system.

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