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$).
Basic drag
Section titled “Basic drag”import { function useRef$<T extends Element = Element>(externalRef?: React.Ref<T> | null): Ref$<T>
Creates an observable element ref. Can be used as a drop-in replacement for
useRef, composed with callback refs, or used with forwardRef.
The element is wrapped with opaqueObject to prevent legendapp/state
from making DOM properties reactive (deep observation).
useRef$, import useDraggable
useDraggable } from "@usels/core";
function function DraggableBox(): React.JSX.Element
DraggableBox() { const const el$: Ref$<HTMLDivElement>
el$ = useRef$<HTMLDivElement>(externalRef?: React.Ref<HTMLDivElement> | undefined): Ref$<HTMLDivElement>
Creates an observable element ref. Can be used as a drop-in replacement for
useRef, composed with callback refs, or used with forwardRef.
The element is wrapped with opaqueObject to prevent legendapp/state
from making DOM properties reactive (deep observation).
useRef$<interface HTMLDivElement
HTMLDivElement>(); const { const x$: any
x$, const y$: any
y$ } = import useDraggable
useDraggable(const el$: Ref$<HTMLDivElement>
el$);
return ( <React.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div React.RefAttributes<HTMLDivElement>.ref?: React.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>
el$} React.HTMLAttributes<HTMLDivElement>.style?: React.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
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
y$.any
get()}px` }}> Drag me </React.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div> );}Axis restriction
Section titled “Axis restriction”Restrict movement to a single axis.
const { x$ } = useDraggable(el$, { axis: "x" }); // horizontal onlyconst { y$ } = useDraggable(el$, { axis: "y" }); // vertical onlyWith handle
Section titled “With handle”Attach drag to a specific handle element instead of the whole target.
import { function useRef$<T extends Element = Element>(externalRef?: React.Ref<T> | null): Ref$<T>
Creates an observable element ref. Can be used as a drop-in replacement for
useRef, composed with callback refs, or used with forwardRef.
The element is wrapped with opaqueObject to prevent legendapp/state
from making DOM properties reactive (deep observation).
useRef$, import useDraggable
useDraggable } from "@usels/core";
function function WithHandle(): void
WithHandle() { const const el$: Ref$<HTMLDivElement>
el$ = useRef$<HTMLDivElement>(externalRef?: React.Ref<HTMLDivElement> | undefined): Ref$<HTMLDivElement>
Creates an observable element ref. Can be used as a drop-in replacement for
useRef, composed with callback refs, or used with forwardRef.
The element is wrapped with opaqueObject to prevent legendapp/state
from making DOM properties reactive (deep observation).
useRef$<interface HTMLDivElement
HTMLDivElement>(); const const handle$: Ref$<HTMLDivElement>
handle$ = useRef$<HTMLDivElement>(externalRef?: React.Ref<HTMLDivElement> | undefined): Ref$<HTMLDivElement>
Creates an observable element ref. Can be used as a drop-in replacement for
useRef, composed with callback refs, or used with forwardRef.
The element is wrapped with opaqueObject to prevent legendapp/state
from making DOM properties reactive (deep observation).
useRef$<interface HTMLDivElement
HTMLDivElement>(); const { const style$: any
style$ } = import useDraggable
useDraggable(const el$: Ref$<HTMLDivElement>
el$, { handle: Ref$<HTMLDivElement>
handle: const handle$: Ref$<HTMLDivElement>
handle$ }); // ...}Container boundary
Section titled “Container boundary”Clamp drag position inside a container element.
const { style$ } = useDraggable(el$, { containerElement: container$,});Restrict to viewport
Section titled “Restrict to viewport”const { style$ } = useDraggable(el$, { restrictInView: true,});Callbacks
Section titled “Callbacks”const { isDragging$ } = useDraggable(el$, { onStart: (pos, e) => { if (someCondition) return false; // cancel drag }, onMove: (pos, e) => console.log(pos), onEnd: (pos, e) => savePosition(pos),});Reactive position update
Section titled “Reactive position update”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;'Type Declarations
Section titled “Type Declarations”export interface Position { x: number; y: number;}export interface UseDraggableOptions { exact?: boolean; preventDefault?: boolean; stopPropagation?: boolean; capture?: boolean; handle?: MaybeElement; containerElement?: MaybeElement; initialValue?: Position; onStart?: (position: Position, event: PointerEvent) => void | false; onMove?: (position: Position, event: PointerEvent) => void; onEnd?: (position: Position, event: PointerEvent) => void; axis?: "x" | "y" | "both"; disabled?: boolean; pointerTypes?: Array<"mouse" | "pen" | "touch">; restrictInView?: boolean;}export interface UseDraggableReturn { x$: Observable<number>; y$: Observable<number>; position$: Observable<Position>; isDragging$: Observable<boolean>; style$: Observable<string>;}export declare function useDraggable(target: MaybeElement, options?: DeepMaybeObservable<UseDraggableOptions>): UseDraggableReturn;Source
Section titled “Source”Contributors
Section titled “Contributors”- tigerwest
Changelog
Section titled “Changelog”a7392ab2026-03-06 - feat(core,browser): add sync strategy hooks (tigerwest)