mrkeyoor.com_
Sat 08 Aug 17:39 UTC
npmWeb Frontendupdated 08 Aug 2026

react-draggable

react-draggable makes one existing React element movable with mouse or touch input. The Draggable component owns x and y state, applies a CSS transform, and adds classes and event handlers without inserting a wrapper. It supports axes, bounds, grid snapping, drag handles, cancellation selectors, scaled parents, controlled positions, and CSP nonces. DraggableCore exposes the same pointer calculations and callbacks without applying styles, for apps that need to own rendering. It is a movement primitive, not a sortable list or full drag-and-drop system.

Verdict

A focused and dependable primitive for making one React element follow a pointer. Choose a drag-and-drop toolkit instead when the interaction has destinations, sorting, collision rules, or accessibility requirements beyond movement.

API stability5/5The 4.x public surface has kept the same Draggable and DraggableCore split since 2019, and the current changelog says the TypeScript source migration preserved the CommonJS export shape and public API. Recent additions such as allowMobileScroll and nonce are optional props. The component still supports React back to 16.3, while nodeRef provides the documented path through modern Strict Mode.
Docs5/5The README documents every public prop with defaults, callback data, controlled and uncontrolled examples, nodeRef and custom-component forwarding, transform replacement, React compatibility, and all three strict-CSP options. A live demo and source are linked. The boundary is also clear: DraggableCore provides deltas but no styles. Complex mobile behavior and accessibility patterns are left to the application because they are outside the primitive's API.
Maintenance5/5Version 4.7.1 was published July 29, 2026 and the repository was pushed August 5, 2026. Recent releases migrated source and declarations to TypeScript, added native ESM, test React 18 and 19 types, run CI on Node 20, 22, and 24, and fixed CSP and bundler regressions. GitHub reports 209 open issues and pull requests, but the active release work addresses current React and browser concerns.
Ecosystem5/5The npm download API reports 6,964,597 downloads in the latest week and the repository has 9,287 stars. React-Resizable and React-Grid-Layout use DraggableCore as a lower-level building block, and the simple callback shape integrates with ordinary React state, canvas transforms, and persistence. Its ecosystem score reflects adoption and composability, not feature breadth; sortable and accessible DnD systems remain separate libraries.

Use it if

  • You need to move a panel, marker, floating control, or canvas item without drop zones or list reordering
  • The element can be positioned with a CSS transform and simple numeric bounds or grid snapping
  • You want uncontrolled dragging for the common case but still need a controlled position for reset or external state
  • A custom renderer needs normalized drag deltas from DraggableCore while retaining complete control of styles
Skip it if

Setup reality

Install react-draggable alongside React and ReactDOM. Version 4 supports React 16.3 and newer, with both listed as peers; 4.7.1 includes CommonJS, ESM, generated TypeScript declarations, and explicit React 18 and 19 type coverage. The component must receive exactly one element it can clone. In current React code, create nodeRef, pass it to Draggable, and attach the same ref to the actual DOM node; otherwise the fallback uses findDOMNode and produces Strict Mode warnings. A custom child must forward that ref and spread incoming props so the library's mouse, touch, style, and class handlers reach the node. Draggable does not add a wrapper. It applies transform directly, overwriting an existing transform on the child, so add your own intermediate element when rotation or scale already lives there. CSS transform movement does not change layout flow. Bounds based on parent or selectors depend on measured DOM geometry, and transformed parents need the correct scale prop or deltas will be wrong. Touch interaction is a product choice: the default prevents touchstart scrolling, while allowMobileScroll preserves scrolling but changes how easily dragging begins. The user-select prevention hack injects a style element on first drag. Under a strict style-src CSP, provide the correct nonce from the first draggable instance or disable the hack and put the documented selection rules in your stylesheet. Controlled mode is not conventional read-only control: the README notes that dragging still occurs unless disabled, so onDrag must synchronously reflect data.position. For snapping, bounds, persistence, or multi-item canvases, decide whether the displayed coordinates or the final onStop values are your source of truth. The library supplies no keyboard dragging, drop semantics, z-index management, collision engine, or auto-scroll system.

Patterns

Make a DOM element draggable in Strict Modemake-element-draggable

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

export function MovableCard() {
  const nodeRef = useRef<HTMLDivElement>(null)
  return (
    <Draggable nodeRef={nodeRef}>
      <div ref={nodeRef}>Drag me</div>
    </Draggable>
  )
}

Pass the same nodeRef to Draggable and the real DOM child. This avoids the findDOMNode fallback and its Strict Mode warning.

Limit dragging to a handledrag-by-handle

<Draggable nodeRef={nodeRef} handle=".drag-handle">
  <section ref={nodeRef}>
    <header className="drag-handle">Move panel</header>
    <input placeholder="Editable content" />
  </section>
</Draggable>

handle is a CSS selector matched below the child. Add an obvious cursor and a separate keyboard movement option for accessibility.

Prevent controls from starting a dragexclude-interactive-controls

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

cancel is also a descendant CSS selector. Without it, pointer interaction on inputs and buttons can become frustrating or ambiguous.

Keep the element inside its parentconstrain-to-parent

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

Bounds use measured DOM geometry. Padding, margins, offset parents, transforms, and a too-small parent can all affect the result.

Snap movement to a gridsnap-to-grid

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

Grid values are x and y increments. The position is still a transform, so snapping does not make surrounding layout reserve grid cells.

Allow horizontal movement onlyrestrict-drag-axis

<Draggable
  nodeRef={nodeRef}
  axis="x"
  bounds={{ left: 0, right: 480, top: 0, bottom: 0 }}
>
  <div ref={nodeRef}>Horizontal slider handle</div>
</Draggable>

axis limits rendered movement, while callback deltas may still reflect pointer activity. Test business logic that consumes onDrag data.

Store and reset a controlled positioncontrol-drag-position

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

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

<button onClick={() => setPosition({ x: 0, y: 0 })}>Reset</button>

A position prop does not disable dragging. Reflect each onDrag value promptly, or pass disabled when the element must be fixed.

Save coordinates when dragging stopspersist-final-position

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

defaultPosition is read only for initial uncontrolled state. Changing savedPosition later will not reposition the element; use position for that.

Correct deltas inside a scaled canvascorrect-scaled-container

<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 parent scale. Nested or nonuniform transforms may require DraggableCore and custom coordinate math.

Preserve a child's own transformcompose-existing-transform

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

Draggable overwrites transform on its direct child. Put application transforms on an inner element or own the combined transform with DraggableCore.

Apply deltas through your own rendereruse-draggable-core

import { DraggableCore } from 'react-draggable'

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

DraggableCore applies no transform or position. Keep state updates functional so rapid drag events do not close over stale coordinates.

Provide a nonce for the selection stylesatisfy-strict-csp

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

The user-select style element is shared and created on the first drag. Pass the same valid nonce consistently, or disable enableUserSelectHack and add the documented CSS yourself.

Alternatives

PackageRegistryPick it when
@dnd-kit/corenpmYou need sensors, collision detection, drag overlays, keyboard input, and accessible sortable or drop-target interactions
react-rndnpmA panel must be both draggable and resizable with min, max, and aspect-ratio constraints
react-dndnpmThe application models typed drag sources and drop targets across a complex React interface
interactjsnpmFramework-neutral dragging, resizing, gestures, inertia, and snapping are needed on arbitrary DOM elements