DOM
useDraggable
Drag an element with pointer or keyboard: bounds, grid, axis lock, smoothing, and jump-to-click.
Demo
live · imported from hookli
- position.x
- 50.0%
- position.y
- 50.0%
- isDragging
- false
Press anywhere to jump, hold to keep dragging. Focus the puck and use the arrow keys.
import { useDraggable } from "hookli";
export function Demo() {
const { ref, containerRef, position, isDragging } = useDraggable<
HTMLDivElement,
HTMLDivElement
>({
unit: "ratio",
bounds: "parent",
initialPosition: { x: 0.5, y: 0.5 },
jumpOnClick: true,
});
return (
<div ref={containerRef} style={{ position: "relative", height: 224 }}>
<div
ref={ref}
tabIndex={0}
aria-label="Draggable puck"
style={{ position: "absolute", top: 0, left: 0, width: 44, height: 44 }}
>
{isDragging ? "●" : "○"}
</div>
<p>
{(position.x * 100).toFixed(1)}% × {(position.y * 100).toFixed(1)}%
</p>
</div>
);
}Usage
import { useDraggable } from "hookli";
export function Demo() {
const { ref, containerRef, position, isDragging } = useDraggable<
HTMLDivElement,
HTMLDivElement
>({
unit: "ratio",
bounds: "parent",
initialPosition: { x: 0.5, y: 0.5 },
jumpOnClick: true,
});
return (
<div ref={containerRef} style={{ position: "relative", height: 224 }}>
<div
ref={ref}
tabIndex={0}
aria-label="Draggable puck"
style={{ position: "absolute", top: 0, left: 0, width: 44, height: 44 }}
>
{isDragging ? "●" : "○"}
</div>
<p>
{(position.x * 100).toFixed(1)}% × {(position.y * 100).toFixed(1)}%
</p>
</div>
);
}API
useDraggable<T extends HTMLElement, C extends HTMLElement>(options?: UseDraggableOptions): UseDraggableReturn<T, C>Parameters
| Name | Type | Default | Description |
|---|---|---|---|
| options | UseDraggableOptions | {} | Every option is optional. See the UseDraggableOptions table below. |
Returns
| Name | Type | Description |
|---|---|---|
| ref | (node: T | null) => void | Callback ref for the draggable element. The hook moves it with transform: translate3d(), so layout is untouched. |
| containerRef | (node: C | null) => void | Callback ref for the canvas that jumpOnClick listens on. |
| position | Position | The current position in unit. In controlled mode it is the position prop. |
| isDragging | boolean | true while a pointer drag is in progress. |
| moveTo | (position: Position, options?: { animate?: boolean }) => void | Move programmatically. Bounds and grid still apply; in controlled mode it calls onChange instead. |
| reset | () => void | Move back to initialPosition. |
UseDraggableOptions
Pass position + onChange for controlled use, or initialPosition for uncontrolled.
| Name | Type | Description |
|---|---|---|
| unit | "px" | "ratio" | px: translate offset from the layout spot. ratio: where the anchor point sits in the bounds box, 0..1 per axis, so it survives resizes. Needs element bounds. |
| position | Position | Controlled position. Input calls onChange; the element moves only when this prop changes. |
| initialPosition | Position | Uncontrolled starting position. |
| onChange | (position: Position) => void | Called when a drag, jump, arrow key or moveTo produces a new position. Not called for prop changes or resize re-clamps. |
| bounds | "parent" | RefObject<HTMLElement> | { left?, top?, right?, bottom? } | Keep the element inside a box. Element bounds are live: re-measured on every gesture and on resize. |
| axis | "x" | "y" | "both" | Restrict movement to one axis. |
| grid | [number, number] | Snap the translate offset to a px grid. |
| jumpOnClick | boolean | Pressing an empty spot of the container jumps the element there; holding keeps dragging. |
| anchor | "center" | "top-left" | The element point that lands on the pointer when jumping, and that a ratio position describes. |
| transition | number | Jump animation length in ms; 0 snaps instantly. |
| smoothing | number | How softly the element trails the pointer, from 0 (glued) to just under 1 (floaty). Reduced motion turns it off. |
| keyboardStep | number | false | Arrow-key step in px when the element is focused; the grid step wins when grid is set. |
| disabled | boolean | Ignore pointer and keyboard input. moveTo still works. |
| onDragStart / onDrag / onDragEnd / onJump | (position: Position) => void | Gesture callbacks, in unit. onDragEnd also fires after each keyboard move. |
Position
A point in the chosen unit.
| Name | Type | Description |
|---|---|---|
| x | number | Horizontal: px offset, or 0..1 of the bounds box width. |
| y | number | Vertical: px offset, or 0..1 of the bounds box height. |