Skip to content
Elements

useDraggable

Makes any element draggable using Pointer Events. Returns Observable values for position (x$, y$), drag state (isDragging$), and a ready-to-use CSS style string (style$).

Draggable
No active drag
Basic
X only
Y only
Toggle
import {
const useRef$: {
<T = any>(initialValue: null): Ref$<T | null>;
<T = any>(initialValue: T): Ref$<T>;
<T = any>(): Ref$<T | null>;
}
useRef$
} from "@usels/core";
import {
const useDraggable: (target: MaybeEventTarget, options?: DeepMaybeObservable<UseDraggableOptions>) => UseDraggableReturn
useDraggable
} from "@usels/web";
function
function DraggableBox(): JSX.Element
DraggableBox
() {
const
const el$: Ref$<HTMLDivElement | null>
el$
=
useRef$<HTMLDivElement>(): Ref$<HTMLDivElement | null> (+2 overloads)

Core (framework-agnostic) version of useRef$. Creates an observable element ref with opaque wrapping.

  • non-null value → Ref$<T>: current, get(), peek() return T
  • null / no arg → Ref$<T | null>: current, get(), peek() return T | null

Nullability is expressed via the type parameter, mirroring T | null at the call site.

useRef$
<
interface HTMLDivElement
HTMLDivElement
>();
const {
const x$: any

Current X position

x$
,
const y$: any

Current Y position

y$
} =
function useDraggable(target: MaybeEventTarget, options?: DeepMaybeObservable<UseDraggableOptions>): UseDraggableReturn

Framework-agnostic draggable Pointer-Events wrapper.

Must be called inside a useScope factory — pointer listeners are registered via createEventListener and cleaned up automatically when the scope disposes. Public behavior matches the legacy useDraggable hook.

@paramtarget - Element to make draggable (Ref$, Observable<Element|null>, raw Element, etc.).

@paramoptions - Configuration (supports DeepMaybeObservable — each field may be an Observable).

useDraggable
(
const el$: Ref$<HTMLDivElement | null>
el$
);
return (
<
JSX.IntrinsicElements.div: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div
RefAttributes<HTMLDivElement>.ref?: Ref<HTMLDivElement> | undefined

Allows getting a ref to the component instance. Once the component unmounts, React will set ref.current to null (or call the ref with null if you passed a callback ref).

ref
={
const el$: Ref$<HTMLDivElement | null>
el$
}
HTMLAttributes<HTMLDivElement>.style?: CSSProperties | undefined
style
={{
StandardLonghandProperties<string | number, string & {}>.position?: Property.Position | undefined

This feature is well established and works across many devices and browser versions. It’s been available across browsers since July 2015.

Syntax: static | relative | absolute | sticky | fixed

Initial value: static

| Chrome | Firefox | Safari | Edge | IE | | :----: | :-----: | :----: | :----: | :---: | | 1 | 1 | 1 | 12 | 4 |

position
: "fixed",
StandardLonghandProperties<string | number, string & {}>.left?: Property.Left<string | number> | undefined

This feature is well established and works across many devices and browser versions. It’s been available across browsers since July 2015.

Syntax: auto | <length-percentage> | <anchor()> | <anchor-size()>

Initial value: auto

| Chrome | Firefox | Safari | Edge | IE | | :----: | :-----: | :----: | :----: | :-----: | | 1 | 1 | 1 | 12 | 5.5 |

left
: `${
const x$: any

Current X position

x$
.
any
get
()}px`,
StandardLonghandProperties<string | number, string & {}>.top?: Property.Top<string | number> | undefined

This feature is well established and works across many devices and browser versions. It’s been available across browsers since July 2015.

Syntax: auto | <length-percentage> | <anchor()> | <anchor-size()>

Initial value: auto

| Chrome | Firefox | Safari | Edge | IE | | :----: | :-----: | :----: | :----: | :---: | | 1 | 1 | 1 | 12 | 5 |

top
: `${
const y$: any

Current Y position

y$
.
any
get
()}px` }}>
Drag me
</
JSX.IntrinsicElements.div: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div
>
);
}

Restrict movement to a single axis.

const { x$ } = useDraggable(el$, { axis: "x" }); // horizontal only
const { y$ } = useDraggable(el$, { axis: "y" }); // vertical only

Attach drag to a specific handle element instead of the whole target.

// @noErrors
import { useRef$ } from "@usels/core";
import { useDraggable } from "@usels/web";
function WithHandle() {
const el$ = useRef$<HTMLDivElement>();
const handle$ = useRef$<HTMLDivElement>();
const { style$ } = useDraggable(el$, { handle: handle$ });
// ...
}

Clamp drag position inside a container element.

const { style$ } = useDraggable(el$, {
containerElement: container$,
});
const { style$ } = useDraggable(el$, {
restrictInView: true,
});
const { isDragging$ } = useDraggable(el$, {
onStart: (pos, e) => {
if (someCondition) return false; // cancel drag
},
onMove: (pos, e) => console.log(pos),
onEnd: (pos, e) => savePosition(pos),
});

x$ and y$ are writable Observables — setting them directly updates style$ and position$.

const { x$, y$, style$, position$ } = useDraggable(el$);
x$.set(100);
y$.set(200);
// style$.get() === 'left: 100px; top: 200px;'
ParameterTypeDescription
targetMaybeEventTarget- Element to make draggable (Ref$, Observable<Element|null>, raw Element, etc.).
optionsUseDraggableOptions (optional)- Configuration (supports DeepMaybeObservable — each field may be an Observable).
OptionTypeDefaultDescription
exactboolean-Only start drag if pointerdown target exactly matches the element. Default: false
preventDefaultboolean-Call preventDefault on pointer events. Default: false
stopPropagationboolean-Call stopPropagation on pointer events. Default: false
captureboolean-Use capture phase for pointerdown. Default: false
handleMaybeEventTarget-Restrict drag to a specific handle element
containerElementMaybeEventTarget-Clamp drag position inside this container element
initialValuePosition-Initial position. Read once at mount time. Default: { x: 0, y: 0 }
onStart((position: Position, event: PointerEvent) => false | void)-Called on drag start. Return false to cancel.
onMove((position: Position, event: PointerEvent) => void)-Called on each drag move
onEnd((position: Position, event: PointerEvent) => void)-Called on drag end
axis"x" | "y" | "both"-Restrict movement axis. Default: ‘both’
disabledboolean-Disable dragging
pointerTypes("mouse" | "pen" | "touch")[]-Filter by pointer type. Default: all types allowed
restrictInViewboolean-Clamp drag position inside the viewport. Default: false
windowWindowSource--

UseDraggableReturn

NameTypeDescription
x$ObservablePrimitive<number>Current X position
y$ObservablePrimitive<number>Current Y position
position$Observable<Position>Current position as { x, y }
isDragging$ObservableBoolean<boolean>Whether currently dragging
style$ObservablePrimitive<string>CSS position string: “left: Xpx; top: Ypx;“

View on GitHub