react-draggable, react-grid-layout, react-resizable, and react-rnd are React libraries that enable interactive, user-manipulable UI elements such as draggable windows, resizable panels, and grid-based dashboards. These packages solve different but overlapping problems around layout manipulation — from basic dragging to full dashboard systems with constrained resizing and snapping behaviors. While some focus on atomic primitives (e.g., making a single div draggable), others provide higher-level abstractions for complex layouts like widget-based dashboards.
Building interactive UIs often requires letting users move or resize elements — think dashboards, floating toolbars, or resizable panels. The four libraries compared here (react-draggable, react-grid-layout, react-resizable, and react-rnd) each tackle parts of this problem, but with very different scopes and assumptions. Let’s cut through the overlap and see where each truly shines.
react-draggable: Pure Dragging PrimitiveThis library adds drag behavior to any element. It doesn’t handle resizing, grids, or layout coordination — just smooth, customizable dragging.
import Draggable from 'react-draggable';
function App() {
return (
<Draggable>
<div style={{ width: 200, height: 100, background: '#eee' }}>
Drag me anywhere
</div>
</Draggable>
);
}
You get callbacks like onStart, onDrag, and onStop, and can constrain movement via bounds or axis locking. But that’s it — no resize handles, no grid.
react-resizable: Resize-Only Building BlockFocused exclusively on resizing, this package provides resizable handles and manages width/height updates. You attach it to an element and define how resizing should behave.
import { Resizable } from 'react-resizable';
function App() {
const [size, setSize] = useState({ width: 200, height: 150 });
return (
<Resizable
width={size.width}
height={size.height}
onResize={(e, { size }) => setSize(size)}
handle={<span className="resize-handle" />}
>
<div style={{ width: size.width, height: size.height, background: '#ddd' }}>
Resize me
</div>
</Resizable>
);
}
Note: it doesn’t include default styling for handles — you must provide your own. Also, no dragging is included.
react-rnd: Drag + Resize in One PackageThis wraps react-draggable and react-resizable into a single component that supports both dragging and resizing out of the box. It’s designed for free-form, unconstrained movement.
import RND from 'react-rnd';
function App() {
return (
<RND
default={{
x: 100,
y: 100,
width: 200,
height: 150
}}
style={{ background: '#ccc' }}
>
Drag & resize me freely
</RND>
);
}
It inherits props from both parent libraries (e.g., dragHandleClassName, minWidth, bounds), making it convenient for floating UIs like devtools panels or chat windows.
react-grid-layout: Grid-Based Dashboard SystemUnlike the others, this isn’t a primitive — it’s a full layout manager. You define a grid (e.g., 12 columns), and children become grid items that snap to cells when dragged or resized.
import GridLayout from 'react-grid-layout';
function App() {
const layout = [
{ i: 'a', x: 0, y: 0, w: 4, h: 2 },
{ i: 'b', x: 4, y: 0, w: 4, h: 2 }
];
return (
<GridLayout className="layout" cols={12} rowHeight={30} width={1200}>
<div key="a">Widget A</div>
<div key="b">Widget B</div>
</GridLayout>
);
}
Key features: responsive breakpoints, collision prevention, and persistent layout state. But everything lives inside a rigid grid — no free-form placement.
These libraries aren’t mutually exclusive — in fact, react-rnd explicitly depends on the first two. But combining them manually requires care.
→ Use react-rnd. It’s already done for you.
→ Don’t use react-rnd. Instead, use react-grid-layout, which natively supports both dragging and resizing within its grid system.
→ Pick the minimal package: react-draggable for drag-only, react-resizable for resize-only. This keeps bundle size lean and avoids unused logic.
If you tried to roll your own using separate libs:
// Manual combo — more code, more edge cases
import Draggable from 'react-draggable';
import { Resizable } from 'react-resizable';
function Panel() {
const [pos, setPos] = useState({ x: 0, y: 0 });
const [size, setSize] = useState({ width: 200, height: 150 });
return (
<Draggable
position={pos}
onStop={(e, data) => setPos({ x: data.x, y: data.y })}
>
<Resizable
width={size.width}
height={size.height}
onResize={(e, { size }) => setSize(size)}
>
<div style={{ ...size, position: 'absolute', ...pos }}>
Content
</div>
</Resizable>
</Draggable>
);
}
But react-rnd gives you this with less boilerplate and better-tested interaction handling.
How do these libraries handle boundaries, collisions, and responsiveness?
react-draggable: Supports bounds (parent, selector, or { left, top, right, bottom }) to limit drag area. No awareness of other draggable elements.
react-resizable: Enforces minWidth/maxWidth and similar height props. No inter-element awareness.
react-rnd: Inherits bounds from react-draggable and size limits from react-resizable. Still no coordination between multiple Rnd instances — they can overlap freely.
react-grid-layout: Prevents widget overlap by design. Uses a collision detection algorithm. Supports responsive layouts via breakpoints and layouts prop:
const layouts = {
lg: [{ i: 'a', x: 0, y: 0, w: 2, h: 2 }],
md: [{ i: 'a', x: 0, y: 0, w: 1, h: 2 }]
};
<ResponsiveGridLayout
layouts={layouts}
breakpoints={{ lg: 1200, md: 996 }}
cols={{ lg: 12, md: 8 }}
/>
This makes it the only choice for true dashboard experiences where widgets must reflow cleanly on resize.
| Scenario | Best Fit | Why |
|---|---|---|
| Floating video call window | react-rnd | Needs drag + resize, no grid, must stay on-screen (bounds="parent") |
| Admin dashboard with charts | react-grid-layout | Widgets must not overlap, reflow on mobile, save positions |
| Draggable notification toast | react-draggable | Only needs movement; resizing would be pointless |
| Code editor with resizable sidebar | react-resizable | Sidebar width adjusts, but doesn’t move vertically/horizontally |
| Whiteboard with free-form sticky notes | react-rnd (or manual combo) | Notes can overlap, no grid alignment needed |
react-rnd in a grid layout: It won’t snap to columns or avoid collisions. You’ll fight the library.react-grid-layout supports free drag: It doesn’t. Everything is quantized to grid cells.react-resizable: The package provides no default CSS — your handles will be invisible without custom styles.position: relative parents and scroll boundaries. Test thoroughly.Ask yourself two questions:
react-grid-layout.react-rnd.If you only need one behavior, go atomic: react-draggable or react-resizable. Keep it simple — no need to pull in extra features you won’t use.
All four libraries are actively maintained and stable. None are deprecated. Choose based on your layout model, not popularity.
Choose react-draggable when you need to make any React element draggable with minimal overhead and fine-grained control over drag behavior. It’s ideal for floating modals, draggable cards, or custom drag interactions where you don’t need resizing. Avoid it if you require built-in resize handles or grid-aware movement — those must be layered on manually.
Choose react-grid-layout when building dashboard-style interfaces with multiple draggable and resizable widgets that snap to a responsive grid. It enforces layout constraints, supports breakpoints, and maintains consistent positioning across screen sizes. Don’t use it for free-form dragging outside a grid or for non-grid-based layouts — it assumes a strict column/row coordinate system.
Choose react-resizable when you only need resizing functionality (not dragging) and want low-level control over resize handles and bounds. It works well for split panes, resizable sidebars, or custom editors where you’ll combine it with other logic. Avoid it if you also need dragging — you’ll have to integrate another library like react-draggable yourself.
Choose react-rnd when you need a self-contained component that supports both free-form dragging and resizing without grid constraints. It combines react-draggable and react-resizable under the hood, offering a convenient API for floating windows or tool palettes. Avoid it for grid-aligned dashboards — it lacks native support for snapping or responsive layout coordination.
A simple component for making elements draggable.
<Draggable>
<div>I can now be moved around!</div>
</Draggable>
| Version | Compatibility |
|---|---|
| 4.x | React 16.3+ |
| 3.x | React 15-16 |
| 2.x | React 0.14 - 15 |
| 1.x | React 0.13 - 0.14 |
| 0.x | React 0.10 - 0.13 |
$ npm install react-draggable
If you aren't using browserify/webpack, a
UMD version of react-draggable is available. It is updated per-release only.
This bundle is also what is loaded when installing from npm. It expects external React and ReactDOM.
If you want a UMD version of the latest master revision, you can generate it yourself from master by cloning this
repository and running $ make. This will create umd dist files in the dist/ folder.
The default export is <Draggable>. At the .DraggableCore property is <DraggableCore>.
Here's how to use it:
// ES6
import Draggable from 'react-draggable'; // The default
import {DraggableCore} from 'react-draggable'; // <DraggableCore>
import Draggable, {DraggableCore} from 'react-draggable'; // Both at the same time
// CommonJS
let Draggable = require('react-draggable');
let DraggableCore = Draggable.DraggableCore;
<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.
View the Demo and its source for more.
import React from 'react';
import ReactDOM from 'react-dom';
import Draggable from 'react-draggable';
class App extends React.Component {
eventLogger = (e: MouseEvent, data: Object) => {
console.log('Event: ', e);
console.log('Data: ', data);
};
render() {
return (
<Draggable
axis="x"
handle=".handle"
defaultPosition={{x: 0, y: 0}}
position={null}
grid={[25, 25]}
scale={1}
onStart={this.handleStart}
onDrag={this.handleDrag}
onStop={this.handleStop}>
<div>
<div className="handle">Drag from here</div>
<div>This readme is really dragging on...</div>
</div>
</Draggable>
);
}
}
ReactDOM.render(<App/>, document.body);
The <Draggable/> component transparently adds draggability to its children.
Note: Only a single child is allowed or an Error will be thrown.
For the <Draggable/> component to correctly attach itself to its child, the child element must provide support
for the following props:
style is used to give the transform css to the child.className is used to apply the proper classes to the object being dragged.onMouseDown, onMouseUp, onTouchStart, and onTouchEnd are used to keep track of dragging state.React.DOM elements support the above properties by default, so you may use those elements as children without any changes. If you wish to use a React component you created, you'll need to be sure to transfer prop.
<Draggable> Props://
// Types:
//
type DraggableEventHandler = (e: Event, data: DraggableData) => void | false;
type DraggableData = {
node: HTMLElement,
// lastX + deltaX === x
x: number, y: number,
deltaX: number, deltaY: number,
lastX: number, lastY: number
};
//
// Props:
//
{
// If set to `true`, will allow dragging on non left-button clicks.
allowAnyClick: boolean,
// Default `false` and default behavior before 4.5.0.
// If set to `true`, the 'touchstart' event will not be prevented,
// which will allow scrolling inside containers. We recommend
// using the 'handle' / 'cancel' props when possible instead of enabling this.
//
// See https://github.com/react-grid-layout/react-draggable/issues/728
allowMobileScroll: boolean,
// Determines which axis the draggable can move. This only affects
// flushing to the DOM. Callbacks will still include all values.
// Accepted values:
// - `both` allows movement horizontally and vertically (default).
// - `x` limits movement to horizontal axis.
// - `y` limits movement to vertical axis.
// - 'none' stops all movement.
axis: string,
// Specifies movement boundaries. Accepted values:
// - `parent` restricts movement within the node's offsetParent
// (nearest node with position relative or absolute), or
// - a selector, restricts movement within the targeted node
// - An object with `left, top, right, and bottom` properties.
// These indicate how far in each direction the draggable
// can be moved.
bounds: {left?: number, top?: number, right?: number, bottom?: number} | string,
// Specifies a selector to be used to prevent drag initialization. The string is passed to
// Element.matches, so it's possible to use multiple selectors like `.first, .second`.
// Example: '.body'
cancel: string,
// Class names for draggable UI.
// Default to 'react-draggable', 'react-draggable-dragging', and 'react-draggable-dragged'
defaultClassName: string,
defaultClassNameDragging: string,
defaultClassNameDragged: string,
// Specifies the `x` and `y` that the dragged item should start at.
// This is generally not necessary to use (you can use absolute or relative
// positioning of the child directly), but can be helpful for uniformity in
// your callbacks and with css transforms.
defaultPosition: {x: number, y: number},
// If true, will not call any drag handlers.
disabled: boolean,
// Default `true`. Adds "user-select: none" while dragging to avoid selecting text.
enableUserSelectHack: boolean,
// Specifies the x and y that dragging should snap to.
grid: [number, number],
// Specifies a selector to be used as the handle that initiates drag.
// Example: '.handle'
handle: string,
// If desired, you can provide your own offsetParent for drag calculations.
// By default, we use the Draggable's offsetParent. This can be useful for elements
// with odd display types or floats.
offsetParent: HTMLElement,
// Called whenever the user mouses down. Called regardless of handle or
// disabled status.
onMouseDown: (e: MouseEvent) => void,
// Called when dragging starts. If `false` is returned any handler,
// the action will cancel.
onStart: DraggableEventHandler,
// Called while dragging.
onDrag: DraggableEventHandler,
// Called when dragging stops.
onStop: DraggableEventHandler,
// If running in React Strict mode, ReactDOM.findDOMNode() is deprecated.
// Unfortunately, in order for <Draggable> to work properly, we need raw access
// to the underlying DOM node. If you want to avoid the warning, pass a `nodeRef`
// as in this example:
//
// function MyComponent() {
// const nodeRef = React.useRef(null);
// return (
// <Draggable nodeRef={nodeRef}>
// <div ref={nodeRef}>Example Target</div>
// </Draggable>
// );
// }
//
// This can be used for arbitrarily nested components, so long as the ref ends up
// pointing to the actual child DOM node and not a custom component.
//
// For rich components, you need to both forward the ref *and props* to the underlying DOM
// element. Props must be forwarded so that DOM event handlers can be attached.
// For example:
//
// const Component1 = React.forwardRef(function (props, ref) {
// return <div {...props} ref={ref}>Nested component</div>;
// });
//
// const nodeRef = React.useRef(null);
// <DraggableCore onDrag={onDrag} nodeRef={nodeRef}>
// <Component1 ref={nodeRef} />
// </DraggableCore>
//
// Thanks to react-transition-group for the inspiration.
//
// `nodeRef` is also available on <DraggableCore>.
nodeRef: React.Ref<typeof React.Component>,
// Much like React form elements, if this property is present, the item
// becomes 'controlled' and is not responsive to user input. Use `position`
// if you need to have direct control of the element.
position: {x: number, y: number}
// A position offset to start with. Useful for giving an initial position
// to the element. Differs from `defaultPosition` in that it does not
// affect the position returned in draggable callbacks, and in that it
// accepts strings, like `{x: '10%', y: '10%'}`.
positionOffset: {x: number | string, y: number | string},
// Specifies the scale of the canvas your are dragging this element on. This allows
// you to, for example, get the correct drag deltas while you are zoomed in or out via
// a transform or matrix in the parent of this element.
scale: number
}
Note that sending className, style, or transform as properties will error - set them on the child element
directly.
<Draggable> is a 'batteries-included' component that manages its own state. If you want to completely
control the lifecycle of the component, use <DraggableCore>.
For some users, they may want the nice state management that <Draggable> provides, but occasionally want
to programmatically reposition their components. <Draggable> allows this customization via a system that
is similar to how React handles form components.
If the prop position: {x: number, y: number} is defined, the <Draggable> will ignore its internal state and use
the provided position instead. Alternatively, you can seed the position using defaultPosition. Technically, since
<Draggable> works only on position deltas, you could also seed the initial position using CSS top/left.
We make one modification to the React philosophy here - we still allow dragging while a component is controlled.
We then expect you to use at least an onDrag or onStop handler to synchronize state.
To disable dragging while controlled, send the prop disabled={true} - at this point the <Draggable> will operate
like a completely static component.
<DraggableCore>For users that require absolute control, a <DraggableCore> element is available. This is useful as an abstraction
over touch and mouse events, but with full control. <DraggableCore> has no internal state.
See React-Resizable and React-Grid-Layout for some usage examples.
<DraggableCore> is a useful building block for other libraries that simply want to abstract browser-specific
quirks and receive callbacks when a user attempts to move an element. It does not set styles or transforms
on itself and thus must have callbacks attached to be useful.
<DraggableCore> takes a limited subset of options:
{
allowAnyClick: boolean,
allowMobileScroll: boolean,
cancel: string,
disabled: boolean,
enableUserSelectHack: boolean,
offsetParent: HTMLElement,
grid: [number, number],
handle: string,
onStart: DraggableEventHandler,
onDrag: DraggableEventHandler,
onStop: DraggableEventHandler,
onMouseDown: (e: MouseEvent) => void,
scale: number
}
Note that there is no start position. <DraggableCore> simply calls drag handlers with the below parameters,
indicating its position (as inferred from the underlying MouseEvent) and deltas. It is up to the parent
to set actual positions on <DraggableCore>.
Drag callbacks (onStart, onDrag, onStop) are called with the same arguments as <Draggable>.
$ npm run dev$ npm testmake release-patch, make release-minor, or make-release-majormake publishMIT