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.
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.
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
- You need sortable lists, cross-container drops, collision detection, drag overlays, or keyboard sensors: this package only reports movement and has no drop-target model
- Accessibility requires built-in keyboard dragging and announcements: its public event types and handlers are mouse and touch oriented, so you must design keyboard controls and ARIA feedback yourself
- The child already relies on its own transform: the README warns that Draggable overwrites it, which requires an intermediate wrapper or DraggableCore with custom composition
- Dragging should reflow surrounding document layout: Draggable moves with CSS transforms regardless of static, relative, or absolute positioning, so sibling layout does not follow the visual position
- You cannot forward a DOM ref and injected props through custom child components: Strict Mode needs nodeRef, and the child must accept the handlers, className, style, and ref that Draggable clones onto it
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
| Package | Registry | Pick it when |
|---|---|---|
| @dnd-kit/core | npm | You need sensors, collision detection, drag overlays, keyboard input, and accessible sortable or drop-target interactions |
| react-rnd | npm | A panel must be both draggable and resizable with min, max, and aspect-ratio constraints |
| react-dnd | npm | The application models typed drag sources and drop targets across a complex React interface |
| interactjs | npm | Framework-neutral dragging, resizing, gestures, inertia, and snapping are needed on arbitrary DOM elements |