useDrag
Location: src/hooks/useDrag.js
Overview
This file exports two hooks for interactive element manipulation:
useDrag-- makes an element draggable with mouse and touch support, optional grid snapping, and bounding constraintsuseResize-- makes an element resizable with mouse support, min/max size constraints, and optional aspect ratio locking
Both hooks provide ref-based binding, meaning you attach the returned ref to the target DOM element.
useDrag
Signature
function useDrag(options?: {
enabled?: boolean,
onDragStart?: () => void,
onDrag?: (x: number, y: number, deltaX: number, deltaY: number) => void,
onDragEnd?: (x: number, y: number) => void,
initialPosition?: { x: number, y: number },
bounds?: { minX?: number, maxX?: number, minY?: number, maxY?: number },
gridSize?: number
}): {
dragRef: React.RefObject<HTMLElement>,
isDragging: boolean,
position: { x: number, y: number },
setPosition: (pos: { x: number, y: number }) => void
}
Parameters
All parameters are optional and passed as a single options object:
| Parameter | Type | Default | Description |
|---|---|---|---|
enabled | boolean | true | Whether dragging is enabled. |
onDragStart | () => void | -- | Callback fired when the user starts dragging. |
onDrag | (x, y, deltaX, deltaY) => void | -- | Callback fired during drag with current position and delta from start. |
onDragEnd | (x, y) => void | -- | Callback fired when the user releases. |
initialPosition | { x, y } | { x: 0, y: 0 } | Starting position. Updates from external changes when not dragging. |
bounds | { minX?, maxX?, minY?, maxY? } | -- | Optional constraints to keep the element within bounds. |
gridSize | number | 0 | Snap-to-grid size in pixels. 0 disables snapping. |
Return Value
| Property | Type | Description |
|---|---|---|
dragRef | React.RefObject<HTMLElement> | Ref to attach to the draggable element. |
isDragging | boolean | true while the user is actively dragging. |
position | { x: number, y: number } | Current position of the element. |
setPosition | (pos) => void | Programmatically set the position. |
Behavior Notes
- Only left-click triggers drag (ignores right-click and middle-click)
- Clicks on interactive elements (
button,input,textarea,select,a,[data-no-drag]) do not start a drag - Text selection is disabled during drag
- Supports both mouse and single-finger touch events
Usage
function DraggableCard() {
const { dragRef, isDragging, position } = useDrag({
initialPosition: { x: 100, y: 100 },
bounds: { minX: 0, minY: 0, maxX: 800, maxY: 600 },
gridSize: 20
});
return (
<div
ref={dragRef}
style={{
transform: `translate(${position.x}px, ${position.y}px)`,
opacity: isDragging ? 0.8 : 1,
cursor: isDragging ? 'grabbing' : 'grab'
}}
>
Drag me!
</div>
);
}
useResize
Signature
function useResize(options?: {
enabled?: boolean,
onResizeStart?: () => void,
onResize?: (width: number, height: number) => void,
onResizeEnd?: (width: number, height: number) => void,
initialSize?: { width: number, height: number },
minSize?: { width: number, height: number },
maxSize?: { width: number, height: number },
aspectRatio?: number | null
}): {
resizeRef: React.RefObject<HTMLElement>,
isResizing: boolean,
size: { width: number, height: number },
setSize: (size: { width: number, height: number }) => void,
handleResizeStart: (e: MouseEvent) => void
}
Parameters
All parameters are optional and passed as a single options object:
| Parameter | Type | Default | Description |
|---|---|---|---|
enabled | boolean | true | Whether resizing is enabled. |
onResizeStart | () => void | -- | Callback fired when resize begins. |
onResize | (width, height) => void | -- | Callback fired during resize with current dimensions. |
onResizeEnd | (width, height) => void | -- | Callback fired when resize completes. |
initialSize | { width, height } | { width: 200, height: 200 } | Starting size. |
minSize | { width, height } | { width: 100, height: 100 } | Minimum allowed dimensions. |
maxSize | { width, height } | { width: Infinity, height: Infinity } | Maximum allowed dimensions. |
aspectRatio | number | null | null | If set (e.g. 16/9), maintains this aspect ratio during resize. |
Return Value
| Property | Type | Description |
|---|---|---|
resizeRef | React.RefObject<HTMLElement> | Ref to attach to the resizable element. |
isResizing | boolean | true while the user is actively resizing. |
size | { width, height } | Current dimensions. |
setSize | (size) => void | Programmatically set the size. |
handleResizeStart | (e) => void | Attach this to a resize handle's onMouseDown to initiate resizing. |
Usage
function ResizablePanel() {
const { resizeRef, size, isResizing, handleResizeStart } = useResize({
initialSize: { width: 300, height: 200 },
minSize: { width: 150, height: 100 },
aspectRatio: 16 / 9
});
return (
<div
ref={resizeRef}
style={{ width: size.width, height: size.height, position: 'relative' }}
>
<p>Resizable content</p>
<div
className="resize-handle"
onMouseDown={handleResizeStart}
style={{ position: 'absolute', bottom: 0, right: 0, cursor: 'se-resize' }}
/>
</div>
);
}
Last updated: 2026-02-07