mrkeyoor.com_
Wed 23 Sept 00:32 UTC
npmWeb Frontendupdated 22 Sept 2026

react-draggable review

react-draggable moves one existing React child in response to mouse or touch input. `Draggable` owns coordinates and writes a CSS translate, while `DraggableCore` only reports position deltas for a renderer you control. The props cover handles, excluded controls, parent or numeric bounds, grid steps, axis locks, controlled coordinates, and scaled ancestors. Version 4.7.1 repairs optional prop typing for React 18 and prevents browser builds without a `process` shim from failing on import. It does not supply drop zones, sorting, collision detection, or keyboard dragging.

Verdict

react-draggable 4.7.1 installed in 1.4 seconds, used 9 MB across 10 packages, and had 0 audit findings in our sandbox; its browser bundle measured 9.1 KB gzipped. It fits direct pointer movement of one React element, while sortable or accessible drag-and-drop products need a higher-level system.

We installed it

Lab card: what happened when we installed react-draggableScreenshot of react-draggable documentation
Install✓ · 1.4s10 packages on disk · 9 MB
ImportESM import works · require() works · CommonJS package with exports map
Browser9.1 KBgzipped (26.8 KB minified), bundled with esbuild
TypesTypeScript types bundled
Known vulns00 critical · 0 high · 0 moderate · 0 low (npm audit)

Answers from our run

Does react-draggable install cleanly?

Yes. In a fresh container with an empty cache, npm install react-draggable finished in 1 seconds, leaving 10 packages and 9 MB on disk. npm audit reported no known vulnerabilities.

How much does react-draggable add to a browser bundle?

9.1 KB gzipped (26.8 KB minified) when the whole package is bundled for the browser with esbuild. Importing only part of it is usually smaller.

Does react-draggable work with both ESM and CommonJS?

Yes. Both import 'react-draggable' and require('react-draggable') worked in Node 22 in our run. The package is published as CommonJS with an exports map.

Does react-draggable include TypeScript types?

Yes, type declarations ship inside the package, so no @types install is needed.

react-draggable or @dnd-kit/core: which should you use?

@dnd-kit/core: Choose it for sensors, collision rules, drag overlays, sortable UIs, and keyboard input. react-draggable 4.7.1 installed in 1.4 seconds, used 9 MB across 10 packages, and had 0 audit findings in our sandbox; its browser bundle measured 9.1 KB gzipped.

When should you not use react-draggable?

The feature sorts items or moves them between containers. There is no destination model, collision strategy, drag overlay, or reorder helper.

API stability5/5The 4.x line has retained its two-part API: Draggable manages transforms and position, while DraggableCore emits movement data. Longstanding props such as bounds, grid, axis, handle, cancel, position, scale, and nodeRef remain available. The 4.6 TypeScript migration explicitly kept the public shape, and the 4.7 additions are optional. Version 4.7.1 fixes React 18 declarations without requiring component changes.
Docs5/5The README defines every prop and callback field, explains transform replacement, shows the Strict Mode ref pattern and custom-component forwarding, describes controlled behavior, and separates DraggableCore's responsibilities. Its strict CSP section gives 3 concrete choices for the injected style. The demo covers visible motion constraints. Keyboard behavior and drop interactions are absent because the package does not implement those features.
Maintenance5/5Version 4.7.1 was published on July 29, 2026, and the repository was pushed again on August 11. GitHub reports 9,290 stars, an unarchived project, and 210 open issues and pull requests in the combined counter. Recent releases moved source to TypeScript, added ESM output and an exports map, tested React 19, fixed React 18 type optionality, handled bundlers without `process`, and added CSP nonce support.
Ecosystem5/5npm recorded 7,487,568 downloads from August 18 through August 24, 2026. React-Grid-Layout and React-Resizable share the same family of movement primitives, while DraggableCore works with custom canvases and external state. React and React DOM are broad peers from 16.3 onward. The surrounding ecosystem is strong for direct movement, though list sorting, drop destinations, resizing, and design transforms live in other packages.

Use it if

  • A floating panel, diagram node, marker, or canvas control needs pointer movement without drop-target rules.
  • Movement can use CSS translate with a fixed axis, grid interval, or measured boundary.
  • The UI wants internal drag state plus a controlled `position` for reset and programmatic movement.
  • A canvas or custom renderer needs normalized deltas from DraggableCore without package-owned styles.
Skip it if

Setup reality

We installed react-draggable 4.7.1 in a clean Node 22 Bookworm sandbox. npm finished in 1.4 seconds, left 10 packages, and consumed 9 MB. The package declares 2 direct dependencies and 2 peers, with 536 KB unpacked. npm audit found 0 known vulnerabilities. It includes TypeScript declarations. The CommonJS package has an exports map, and both require and ESM import worked for us. Our browser build measured 26.8 KB minified and 9.1 KB gzipped.

React and React DOM 16.3 or later are peers. Draggable expects one child. Under Strict Mode, create a nodeRef, pass it to Draggable, and attach the same ref to the underlying DOM element. A custom component must forward that ref and pass through the cloned handlers, class name, and style. Missing either part can bring back findDOMNode warnings or leave the drag inert.

The direct child receives a translate transform and stays in its original document-flow slot. Put any application rotation or scale on a nested element. Geometry for bounds depends on the DOM, including margins, padding, transformed ancestors, and the offset parent. Version 4.7.1 supports a scale prop for scaled canvases. With a controlled position, dragging still runs; update coordinates from onDrag or set disabled when movement should stop.

Touch drag competes with page scroll. allowMobileScroll lets scrolling continue, which also changes gesture behavior. On the first drag, the default selection fix inserts a shared style element. A strict style-src policy needs the matching nonce, webpack's configured nonce, or enableUserSelectHack={false} plus your own CSS. The library leaves focus, keyboard movement, announcements, auto-scroll, stacking, and all drop semantics to the application.

Patterns

Connect the Strict Mode ref drag-dom-element

import {useRef} from 'react';
import Draggable from 'react-draggable';

function MovableCard() {
  const nodeRef = useRef(null);
  return (
    <Draggable nodeRef={nodeRef}>
      <div ref={nodeRef}>Move this card</div>
    </Draggable>
  );
}

nodeRef and the child's ref must be the same object and must resolve to a DOM element.

Make a custom child draggable forward-custom-child

const Panel = forwardRef(function Panel(props, ref) {
  return <section ref={ref} {...props} />;
});

<Draggable nodeRef={nodeRef}>
  <Panel ref={nodeRef}>Panel contents</Panel>
</Draggable>;

Forward the ref and spread incoming props so the cloned handlers, styles, and classes reach the DOM node.

Start dragging from a header restrict-drag-handle

<Draggable nodeRef={nodeRef} handle=".panel-handle">
  <section ref={nodeRef}>
    <header className="panel-handle">Move panel</header>
    <input aria-label="Panel title" />
  </section>
</Draggable>

handle is a CSS selector for pointer initiation. Provide separate keyboard movement controls.

Keep controls out of drag initiation exclude-form-controls

<Draggable
  nodeRef={nodeRef}
  cancel="input, textarea, button, a, [data-no-drag]"
>
  <div ref={nodeRef}>
    <button>Save</button>
    <p>Drag from this text</p>
  </div>
</Draggable>

Matching descendants remain interactive instead of starting a drag gesture.

Keep a marker inside its stage constrain-to-parent

<div className="stage">
  <Draggable nodeRef={nodeRef} bounds="parent">
    <div ref={nodeRef}>Marker</div>
  </Draggable>
</div>

Parent bounds rely on measured geometry, so test padding, margins, and transformed ancestors in the target browsers.

Move in 16-pixel steps snap-to-grid

<Draggable
  nodeRef={nodeRef}
  grid={[16, 16]}
  defaultPosition={{x: 32, y: 48}}
>
  <div ref={nodeRef}>Grid item</div>
</Draggable>

Grid snapping changes translate coordinates; surrounding layout does not reserve snapped cells.

Reset from React state control-drag-position

const [position, setPosition] = useState({x: 0, y: 0});

<Draggable
  nodeRef={nodeRef}
  position={position}
  onDrag={(_event, data) => setPosition({x: data.x, y: data.y})}
>
  <div ref={nodeRef}>Controlled item</div>
</Draggable>;

Supplying position does not disable pointer input. Mirror onDrag updates or set disabled.

Persist coordinates after dragging save-final-position

<Draggable
  nodeRef={nodeRef}
  defaultPosition={savedPosition}
  onStop={(_event, data) => savePosition({x: data.x, y: data.y})}
>
  <div ref={nodeRef}>Remember me</div>
</Draggable>

defaultPosition is read for initial uncontrolled state. Use position if later storage updates must move the item.

Account for a zoomed canvas correct-scaled-coordinates

<div style={{transform: 'scale(0.75)', transformOrigin: '0 0'}}>
  <Draggable nodeRef={nodeRef} scale={0.75}>
    <div ref={nodeRef}>Accurate at 75%</div>
  </Draggable>
</div>

scale must match the effective ancestor scale; nested or nonuniform transforms can require custom coordinate handling.

Put rotation on an inner wrapper preserve-child-transform

<Draggable nodeRef={nodeRef}>
  <div ref={nodeRef}>
    <div style={{transform: 'rotate(4deg)'}}>Rotated content</div>
  </div>
</Draggable>

Draggable writes transform on its immediate child, so application transforms belong on another element.

Update a custom renderer consume-core-deltas

import {DraggableCore} from 'react-draggable';

<DraggableCore
  nodeRef={nodeRef}
  onDrag={(_event, data) => setPoint((point) => ({
    x: point.x + data.deltaX,
    y: point.y + data.deltaY,
  }))}
>
  <canvas ref={nodeRef} width={800} height={600} />
</DraggableCore>

DraggableCore applies no translate or stored position. Your renderer owns every visual update.

Pass a CSP nonce authorize-selection-style

<Draggable nodeRef={nodeRef} nonce={cspNonce}>
  <div ref={nodeRef}>CSP-compatible item</div>
</Draggable>

The shared selection style is inserted on the first drag, so every possible first instance should receive the valid nonce.

Alternatives

PackageRegistryPick it when
@dnd-kit/corenpmChoose it for sensors, collision rules, drag overlays, sortable UIs, and keyboard input.
react-rndnpmChoose it when the same panel also needs resize handles and size constraints.
react-moveablenpmChoose it for design-surface controls such as resizing, rotation, scaling, warping, and guides.

More web frontend guides

postcss · react · react-dom · tailwindcss · htmlparser2 · tailwind-merge · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.