All questions

useDraggable

Premium

useDraggable

Drag-to-move is a staple: a floating panel, a slider thumb, a sortable card, a resizable divider. The mechanics are always the same — on pointer down, remember where the pointer and the element started; on pointer move, offset the element by how far the pointer traveled; on pointer up, stop. useDraggable packages that into a { position, isDragging, handlers } hook using the unified Pointer Events API (mouse, touch, and pen at once).

Implement useDraggable({ initialPosition }). Return the current { x, y } position, an isDragging flag, and handlers.onPointerDown to spread on the drag handle. During a drag, position = startPosition + (pointer − origin); the move/up listeners live on window so the drag survives the pointer leaving the element.

Signature

function useDraggable({ initialPosition = { x: 0, y: 0 } }) {
  // returns { position, isDragging, handlers: { onPointerDown } }
}

Examples

const { position, isDragging, handlers } = useDraggable();
<div
  {...handlers}
  style={{ transform: `translate(${position.x}px, ${position.y}px)`,
           cursor: isDragging ? 'grabbing' : 'grab' }}
/>
const { position } = useDraggable({ initialPosition: { x: 40, y: 40 } });
// starts offset by 40,40; drags relative to there

Notes

  • Delta math — capture the pointer origin and the element's startPosition on pointer down; on move, position = startPosition + (pointerNow − origin).
  • Listen on window — attach pointermove/pointerup to window (not the element) so a fast drag that outruns the cursor doesn't drop.
  • Stable handler, clean teardownonPointerDown should keep one identity; remove the window listeners on pointer up and on unmount.
  • Continue from where you are — a new drag starts from the current position, so drags accumulate.

Unlock the solution & editor

  • Runnable editor + tests

    Solve in the browser with instant Jest feedback.

  • Detailed solutions

    Walkthroughs, edge cases, and complexity notes.

  • Multi-framework variants

    React, Vue, Vanilla, Angular — same question, different stacks.

Upgrade to Premium