Skip to main content
Version: next

useDrag

useDrag wires up Gesture.Pan() with live position tracking, optional bounds, edge rubber-banding, and momentum on release: everything you'd otherwise hand-roll with withSpring/withDecay for a draggable element. Position persists across drags instead of resetting each time.

Signature

function useDrag<T extends HTMLElement>(
ref: RefObject<T>,
options?: UseDragOptions
): UseDragResult;
interface UseDragOptions {
enabled?: boolean;
axis?: 'x' | 'y';
minDistance?: number;
initial?: { x?: number; y?: number };
bounds?: DragBounds | RefObject<HTMLElement>;
elastic?: boolean | number;
momentum?: boolean;
onStart?: (e: PanEvent) => void;
onChange?: (e: PanEvent) => void;
onEnd?: (e: PanEvent) => void;
}

interface DragBounds {
left?: number;
right?: number;
top?: number;
bottom?: number;
}

interface UseDragResult {
x: AnimateValue<number>;
y: AnimateValue<number>;
isDragging: boolean;
controls: Controls;
}

Basic usage

import { useRef } from 'react';
import { useDrag, animate } from 'react-ui-animate';

function DraggableBox() {
const ref = useRef(null);
/* x / y are AnimateValues — bind them to transforms */
const { x, y } = useDrag(ref);

return (
<animate.div
ref={ref}
style={{
translateX: x,
translateY: y,
width: 100,
height: 100,
background: '#60a5fa',
cursor: 'grab',
}}
/>
);
}
Preview

x and y are AnimateValues, so they can be read directly in style (as above) or combined/interpolated like any other animated value.

tip

Animate translateX / translateY, not left / top. Transforms stay on the GPU and avoid layout thrash while dragging.

Bounds, elasticity, and momentum

/* Constrain drag, rubber-band at edges, fling on release */
useDrag(ref, {
bounds: { left: -100, right: 100, top: -100, bottom: 100 },
elastic: true,
momentum: true,
});
  • bounds accepts a fixed { left, right, top, bottom } box or a container ref to constrain against.
  • elastic controls the rubber-band feel while dragging past bounds. true uses the default elastic constant; a number sets a custom one; false hard-clamps instead. Momentum on release always hard-stops at bounds regardless of this setting.
  • momentum flings the element on release using the pointer's release velocity (via withDecay under the hood); set false for a hard stop.
Preview

Other options

  • axis: 'x' | 'y' locks movement to one axis.
  • minDistance requires the pointer to travel that many pixels before the drag is recognized (useful to avoid hijacking clicks/taps).
  • initial: { x, y } sets the starting position, read once on mount, like useValue's initial value.
  • enabled: false disables the gesture without unmounting it.
  • onStart / onChange / onEnd receive the same PanEvent shape as Gesture.Pan(): use these when you need side effects beyond position (e.g. triggering a dismiss action).

Multiple draggable elements

useDrag takes a single ref. For several independent draggables, call it once per element (typically by extracting a small child component), rather than passing an array. Each call gets its own bounds/position/state.

isDragging and controls

const { x, y, isDragging, controls } = useDrag(ref);

isDragging is true while actively pressed and moving. controls.reset() / .cancel() / .pause() / .resume() act on the current release animation (spring-to-bounds or momentum decay).

Best practices

Do

  • Animate translateX/translateY, not left/top.
  • Prefer bounds + elastic/momentum over hand-rolling withSpring snap-back logic. It's the same physics, already wired up.
  • Provide a visual cue for draggability (cursor: 'grab' / 'grabbing') and a non-gesture fallback where the interaction matters (e.g. buttons alongside a draggable slider).

Don't

  • Don't set minDistance to 0 if the element also handles clicks. Give clicks a little slop so they don't get swallowed as micro-drags.
  • Don't pass a new ref object on every render. A stable ref is required since registration happens once on mount.

Next steps

  • useGesture: the lower-level Gesture.Pan() builder this hook is built on, plus the other seven gesture types.
  • withDecay: the momentum animation useDrag uses internally for momentum: true.