Draggable

DnD & Interaction

Wrapper that makes any element draggable.

Preview

Drag me around!

Usage

example.jsx
import { Draggable } from "@/components/ui/draggable";

export default function Example() {
  return <Draggable />;
}

Source Code

Copy this file into components/ui/draggable.jsx in your project.

draggable.jsx
"use client";

import { forwardRef, useRef, useState, useCallback } from "react";
import { cn } from "@/lib/utils";

const Draggable = forwardRef(({ className, children, onDragEnd: onDragEndProp, ...props }, ref) => {
  const [pos, setPos] = useState({ x: 0, y: 0 });
  const startRef = useRef({ x: 0, y: 0, px: 0, py: 0 });

  const onMouseDown = useCallback((e) => {
    e.preventDefault();
    startRef.current = { x: pos.x, y: pos.y, px: e.clientX, py: e.clientY };
    const onMove = (e) => {
      setPos({
        x: startRef.current.x + e.clientX - startRef.current.px,
        y: startRef.current.y + e.clientY - startRef.current.py,
      });
    };
    const onUp = () => {
      window.removeEventListener("mousemove", onMove);
      window.removeEventListener("mouseup", onUp);
      onDragEndProp?.(pos);
    };
    window.addEventListener("mousemove", onMove);
    window.addEventListener("mouseup", onUp);
  }, [pos, onDragEndProp]);

  return (
    <div ref={ref} className={cn("cursor-grab active:cursor-grabbing", className)}
      style={{ transform: `translate(${pos.x}px, ${pos.y}px)` }}
      onMouseDown={onMouseDown}
      {...props}
    >
      {children}
    </div>
  );
});
Draggable.displayName = "Draggable";

export { Draggable };

Quick Install

Make sure you have the cn() utility set up. It requires clsx and tailwind-merge.

npm install clsx tailwind-merge