# useDraggable > Drag an element with pointer or keyboard: bounds, grid, axis lock, smoothing, and jump-to-click. - Category: DOM - Package: `hookli` (install with `npm i hookli`) - Docs: https://hookli.vercel.app/docs/use-draggable ## Signature ```ts useDraggable(options?: UseDraggableOptions): UseDraggableReturn ``` ## Parameters - `options`: `UseDraggableOptions` (default: `{}`) — Every option is optional. See the UseDraggableOptions table below. ## Returns - `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. ## Types ### UseDraggableOptions Pass position + onChange for controlled use, or initialPosition for uncontrolled. - `unit`: `"px" | "ratio"` (default: `"px"`) — 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` (default: `{ x: 0, y: 0 }`) — 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 | { 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"` (default: `"both"`) — Restrict movement to one axis. - `grid`: `[number, number]` — Snap the translate offset to a px grid. - `jumpOnClick`: `boolean` (default: `false`) — Pressing an empty spot of the container jumps the element there; holding keeps dragging. - `anchor`: `"center" | "top-left"` (default: `"center"`) — The element point that lands on the pointer when jumping, and that a ratio position describes. - `transition`: `number` (default: `200`) — Jump animation length in ms; 0 snaps instantly. - `smoothing`: `number` (default: `0.3`) — How softly the element trails the pointer, from 0 (glued) to just under 1 (floaty). Reduced motion turns it off. - `keyboardStep`: `number | false` (default: `10`) — Arrow-key step in px when the element is focused; the grid step wins when grid is set. - `disabled`: `boolean` (default: `false`) — 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. - `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. ## Usage ```tsx 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 (
{isDragging ? "●" : "○"}

{(position.x * 100).toFixed(1)}% × {(position.y * 100).toFixed(1)}%

); } ```