Library / SDK
react-grid-layout/react-draggable avatar
react-grid-layout/react-draggable

react-draggable moves your element with a CSS transform and adds no DOM wrapper

React draggable component

9,291 stars1,048 forksJavaScriptMIT

At a glance

What is it?
react-draggable is a two-component drag layer: Draggable manages position and styles for you, DraggableCore hands you the raw callbacks and leaves positioning to you. The friction lives in four places: a nodeRef that Strict Mode demands, a className prop that throws, a user-select style tag that strict CSPs reject, and a build that assumes Yarn.
Who is it for?
react-draggable is the right choice when you need one element to move and want the library to own the transform, the drag classes, and the coordinate math, especially inside a transformed parent where passing scale saves you the arithmetic. Choose DraggableCore instead when the layout engine is already yours, and accept that you then write the transform yourself.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 13 days ago.
What is it written in?
Mainly JavaScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on November 13, 2026, and from our analysis. They are not legal advice.

From daily snapshots

Star history

  • react-grid-layout/react-draggable9,291 ★ · +2
Star history
Repository2026-09-222026-11-13
react-grid-layout/react-draggable9.3k9.3k

2 Stars in the last 52 days, measured from the daily GitHub snapshot. Compare with other projects

Editorial analysis

Draggable extends your element instead of wrapping it in a new node

The smallest possible usage is three lines, with no props at all:

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

What that buys you is event handlers and styles attached to an element you already wrote. Draggable does not create a wrapper element in the DOM, so your selectors, your flex parent, and your layout keep working unchanged. Movement is done with CSS transforms, which means the element can be dragged whatever its current positioning is, relative, absolute, or static, and it can be moved again between drags without breaking. The same property is the limitation. If the element you are dragging already carries a CSS transform, Draggable overwrites it, and the fix is an intermediate wrapper such as `<Draggable><span>...</span></Draggable>` so the outer element owns the drag transform and the inner one keeps yours. Any design that relies on a rotate or scale on the same node has to be split across two elements.

Strict Mode wants a nodeRef because Draggable otherwise finds the DOM node itself

The working example from the project passes a ref to both sides:

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

The reason is stated plainly: passing nodeRef avoids ReactDOM.findDOMNode() deprecation warnings in React Strict Mode. Without it the component resolves its own DOM node, which is exactly the call Strict Mode is designed to retire, so the console fills with warnings before anything is visibly wrong. Custom child components need the same treatment, forwarding the ref along with the props:

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

The cost of a custom child is that you now own ref forwarding, and a component that forgets to forward breaks dragging.

DraggableCore drops the state, the styles, and the transform, keeping only callbacks

For full control, DraggableCore exposes the drag callbacks without managing state or styles. It sets no transform at all, so positioning becomes your job, which is the entire trade. What survives on it is a smaller prop list than Draggable has: `allowAnyClick`, `allowMobileScroll`, `cancel`, `disabled`, `enableUserSelectHack`, `grid`, `handle`, `nodeRef`, `offsetParent`, `onDrag`, `onMouseDown`, `onStart`, `onStop`, and `scale`. Everything stateful is gone, so there is no `position` and no `defaultPosition`. The handlers receive the same data shape as Draggable's:

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

type DraggableData = {
  node: HTMLElement,
  x: number, y: number,
  deltaX: number, deltaY: number,
  lastX: number, lastY: number,
};

Returning `false` from onStart cancels the drag. For the combination of drag plus resize, the project points at React-Resizable and React-Grid-Layout as the places to read, and Draggable is described as the batteries-included component that manages its own state for anyone who does not want to take that on.

enableUserSelectHack injects a style tag, so a strict CSP needs the nonce prop

The default value of `enableUserSelectHack` is `true`, and what it does is add `user-select: none` while dragging, which stops the browser from selecting the text you happen to drag across. It does that by injecting a `<style>` element into the document. That single design decision is the one place react-draggable touches your page's security policy, and the props table answers it with one line: `nonce` is a CSP nonce for the injected user-select `<style>` element. Under a Content Security Policy without a matching nonce, the style is refused, the guard is gone, and dragging an element over a paragraph starts selecting that paragraph. Setting `enableUserSelectHack` to `false` removes the behaviour rather than fixing the policy. So the decision you actually make is whether to pass a nonce, disable the hack, or run a permissive policy, and the project leaves that choice to the host application.

Constraints are selector-based, so the arithmetic is yours when the parent is transformed

The bounding behaviour comes from four props that each solve a different problem. `axis` takes `'both'`, `'x'`, `'y'`, or `'none'` and defaults to `'both'`. `bounds` restricts movement using `'parent'`, a CSS selector, or an object of left, top, right, and bottom. `grid` takes an `[x, y]` pair and snaps movement to it. `scale`, defaulting to `1`, is the one that catches people out: it exists for dragging inside transformed parents, so if an ancestor is scaled the library needs to know by how much or every delta is wrong. `offsetParent` overrides the element used as the origin when it cannot be inferred. Two smaller ones change what starts a drag rather than where it goes: `handle` is a selector for the drag handle, `cancel` is a selector for elements that should not initiate one, and `allowAnyClick`, false by default, enables dragging on non-left-button clicks. `allowMobileScroll`, also false by default, decides whether `touchstart` is prevented, so leaving it alone means containers will not scroll while a touch lands on the element.

Passing className, style, or transform to Draggable is an error, not a merged prop

This is the sharpest edge in the API. Setting `className`, `style`, or `transform` on Draggable raises an error; those belong on the child element instead. The reason is that all three collide with what Draggable itself manages, since it drives the transform and swaps classes during the drag. The class names it applies have their own props, and the defaults are readable straight from the table: `defaultClassName` is `'react-draggable'`, `defaultClassNameDragging` is `'react-draggable-dragging'`, and `defaultClassNameDragged` is `'react-draggable-dragged'`. So styling is done through those three names on the child, or through your own selectors, and the component refuses the shortcut rather than quietly dropping one of the two writers. Practically, code that passes a className to make a draggable element pick up a shared utility class will throw at render, which is a loud failure but one you meet on the first day if you migrate from a wrapper-based library.

The package ships three module formats and the build verifies the published contracts

package.json names `build/cjs/cjs.js` as main, `build/cjs/cjs.mjs` as module, and `build/web/react-draggable.min.js` as the unpkg entry, with an exports map that splits the types for the import and require conditions, so ES Modules and CommonJS each get their own declaration file:

js
// 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, and the published files list contains only `build`. The Makefile explains how that output is produced: tsup emits CommonJS, ESM, and declarations into build/cjs and rewrites cjs.js into the legacy `module.exports === Draggable` shape, while webpack emits the UMD global bundle, and `scripts/verify-build.cjs` runs afterwards to check the published contracts. It also makes clean an order-only prerequisite on both leaf targets, specifically so that parallel make does not run `rm -rf build` in the middle of a write. The catch for contributors is the package manager: the Makefile is written around Yarn 4, and `test:browser` literally begins with `yarn build`.

Version 4.7.2 in package.json, while the visible release tags stop in 2016

Two version signals in this repository disagree. package.json declares version 4.7.2, and the release list shows v2.2.3, v2.2.2, and v2.2.1, all dated December 8, 2016. So the tags are not a guide to the current line of work; the commit history is where recent activity is, with the last push dated November 1, 2026. The compatibility table is the part that actually constrains you, because it maps each major to a React version: 4.x needs React 16.3 or later, 3.x covers React 15 to 16, and 2.x covers React 0.14 to 15. That floor is why nodeRef matters as much as it does, since the modern path arrived with the React version that also introduced Strict Mode warnings about findDOMNode. If you are on React 16.3 or newer, the current version is the one to install, and pinning by an old tag from 2016 would put you on a two-generation-old component whose peer expectations no longer match your app.

Editorial conclusion

react-draggable is the right choice when you need one element to move and want the library to own the transform, the drag classes, and the coordinate math, especially inside a transformed parent where passing scale saves you the arithmetic. Choose DraggableCore instead when the layout engine is already yours, and accept that you then write the transform yourself. Check two things before you commit: whether your app runs in Strict Mode, because without nodeRef the component reaches for findDOMNode and warns, and whether you have already set className, style, or transform on the element, because passing any of those to Draggable is an error rather than a no-op. Also read enableUserSelectHack, which defaults to true and injects a style element your Content Security Policy may block unless you supply the nonce prop.

Frequently asked questions

what is react draggable

It is a component that makes an existing element draggable. Draggable wraps your element and extends it with event handlers and styles without creating a wrapper element in the DOM, moving it with CSS transforms. DraggableCore offers the same callbacks with no state, no styles, and no transform.

how to use react draggable

Install it, then wrap the element you want to move and pass a nodeRef created with useRef to both Draggable and the child. Custom children need forwardRef so the ref reaches the DOM node. Without nodeRef the component calls ReactDOM.findDOMNode, which Strict Mode warns about.

install react draggable

The install step is `npm install react-draggable` or `yarn add react-draggable`. TypeScript types ship with the package, and version 4.x requires React 16.3 or later, with 3.x covering React 15 to 16 and 2.x covering React 0.14 to 15.

react draggable and resizable

The project points at React-Resizable and React-Grid-Layout as the places to read for the combined case. The reason the pairing works is DraggableCore, which provides the drag callbacks without managing state or styles and sets no transform, leaving the positioning to whatever layout code you write.

Official sources

  1. Issues
  2. License: MIT
  3. react-grid-layout/react-draggable on GitHub
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/react-grid-layout-react-draggable.svg)](https://hysenlabs.com/projects/react-grid-layout-react-draggable)

Star history chart for your README

markdown
[![Star history](https://hysenlabs.com/chart/react-grid-layout-react-draggable.svg)](https://hysenlabs.com/projects/react-grid-layout-react-draggable)

Was this useful?