All hooks / Events and input

useDrag

495 B minified and brotliedSourceMarkdown✓ Works in all modern browsers

Calls onDrag while the user drags an element with a mouse, finger, or pen. Returns a callback ref for the element (the handle).

Captures the pointer, so the drag keeps going when the pointer leaves the element or the window, and ends on release or cancel. Movements under threshold don't start a drag, so a click on the handle stays a click. Text selection is blocked while dragging. Always calls the latest onDrag.

Import

import { useDrag } from '@vincent.ri/react-tils'
// or only this hook
import { useDrag } from '@vincent.ri/react-tils/useDrag'

Usage

// A splitter that resizes the sidebar.
const [width, setWidth] = useState(280)
const handle = useDrag(({ deltaX }) => setWidth((w) => Math.min(600, Math.max(160, w + deltaX))))
return (
  <div style={{ display: 'flex' }}>
    <aside style={{ width }}>…</aside>
    <div ref={handle} role="separator" style={{ width: 6, cursor: 'col-resize', touchAction: 'none' }} />
    <main>…</main>
  </div>
)

Live demo

Drag the bar between the panes, even past the edge.

left
right
left pane: 140px

When to use it

Resizable panels and splitters, custom sliders, dragging items on a canvas or board, or pan to scroll.

When not to

You only need to know a swipe happened and its direction; use useSwipe. For reordering lists, a drag-and-drop library handles keyboard access and drop targets. Give touch handles CSS touch-action: none, or the browser scrolls instead of dragging.

API

function useDrag<T extends HTMLElement = HTMLElement>(onDrag: (drag: DragState) => void, options?: DragOptions): (element: T | null) => void
ParameterDescription
onDragCalled on every move with dx, dy, deltaX, deltaY, first, last, and the event. Can change every render.
optionsenabled, threshold. Optional.

Returns A callback ref. Pass it to the element to drag.

Types

export type DragState = {
  /** Distance moved since the drag started, in pixels. */
  dx: number;
  dy: number;
  /** Distance moved since the previous call. */
  deltaX: number;
  deltaY: number;
  /** The first call of this drag. */
  first: boolean;
  /** The last call: the pointer was released or the drag was cancelled. */
  last: boolean;
  event: PointerEvent;
};
export type DragOptions = {
  /** Listen while `true`. Default `true`. */
  enabled?: boolean;
  /** Pixels the pointer must move before a drag starts, so clicks stay clicks. Default `3`. */
  threshold?: number;
};

Browser support

UsesChromeEdgeFirefoxSafariChrome AndroidSafari iOS
Pointer eventsWidely available55+12+59+13+55+13+
setPointerCaptureWidely available55+12+59+13+55+13+

First version with support, from MDN's compatibility data (web-features 3.42.0, @mdn/browser-compat-data 8.1.5).

Coming from another library?

Other libraries call this useDraggable, useGesture (onDrag), usePointerDrag.