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.
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.
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>
);
}
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>
);
}
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>
);
}
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>
);
}
Despite their different goals, these libraries share common patterns and dependencies.
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>
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
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)} />
| Feature | react-draggable | react-grid-layout | react-resizable |
|---|---|---|---|
| Primary Goal | Move single items freely | Manage complex grid dashboards | Change 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 For | Modals, Lists, Games | Admin Panels, Dashboards | Split-panes, Image Editors |
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.
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.
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.
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.
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