useDraggable

Tracks a pointer drag and exposes a CSS transform offset. It distinguishes a tap from a drag with a small threshold (so a click still registers), and divides movement by the rendered scale so dragging feels 1:1 even inside a zoomed canvas. Mouse and pen drag; touch is reserved for canvas pan/zoom.

Drag me

Installation

npx shadcn@latest add https://canvas.blode.co/r/use-draggable.json

Usage

const { ref, dragging, transform, onPointerDown, onPointerMove, onPointerUp } =
  useDraggable();
 
return (
  <div
    onPointerDown={onPointerDown}
    onPointerMove={onPointerMove}
    onPointerUp={onPointerUp}
    ref={ref}
    style={{ cursor: dragging ? "grabbing" : "grab", transform }}
  >
    Drag me
  </div>
);

onPointerUp returns true if the gesture was a drag and false if it was a tap, handy for firing a click action only when the element wasn't moved.

Returns

PropTypeDefaultDescription
refRef<HTMLDivElement>Attach to the draggable element.
transformstring | undefinedThe current translate, for the element's `style.transform`.
draggingbooleanTrue while an active drag is in progress.
onPointerDown(e) => voidPointer-down handler, starts tracking.
onPointerMove(e) => voidPointer-move handler, updates the offset.
onPointerUp() => booleanPointer-up handler, returns whether it was a drag.