@neodrag/solid
A lightweight directive to make your elements draggable.
npm i @neodrag/solid@next
Usage
Basic usage
import { useDraggable } from '@neodrag/solid';
export const App: Component = () => {
const [draggableRef, setDraggableRef] =
createSignal<HTMLElement | null>(null);
useDraggable(draggableRef);
return <div ref={setDraggableRef}>You can drag me</div>;
};
Note: Neodrag uses
useDraggable
instead ofcreateDraggable
because it’s built on a shared DraggableFactory instance from@neodrag/core
, providing consistent APIs across all frameworks and better performance through shared event delegation.
With plugins
import { useDraggable, axis, grid } from '@neodrag/solid';
export const App: Component = () => {
const [draggableRef, setDraggableRef] =
createSignal<HTMLElement | null>(null);
useDraggable(draggableRef, [axis('x'), grid([10, 10])]);
return <div ref={setDraggableRef}>Horizontal grid snapping</div>;
};
Defining plugins elsewhere with TypeScript
import {
useDraggable,
axis,
bounds,
BoundsFrom,
type Plugin,
} from '@neodrag/solid';
export const App: Component = () => {
const [draggableRef, setDraggableRef] =
createSignal<HTMLElement | null>(null);
const plugins: Plugin[] = [axis('y'), bounds(BoundsFrom.parent())];
useDraggable(draggableRef, plugins);
return <div ref={setDraggableRef}>Type-safe dragging</div>;
};
Getting drag state
import { useDraggable } from '@neodrag/solid';
export const App: Component = () => {
const [draggableRef, setDraggableRef] =
createSignal<HTMLElement | null>(null);
const dragState = useDraggable(draggableRef);
createEffect(() => {
console.log('Position:', dragState().offset);
console.log('Is dragging:', dragState().isDragging);
});
return (
<div ref={setDraggableRef}>Check console while dragging</div>
);
};
Reactive Plugins with createCompartment
For dynamic behavior that changes during runtime:
import { createSignal } from 'solid-js';
import {
useDraggable,
axis,
createCompartment,
} from '@neodrag/solid';
export const App: Component = () => {
const [draggableRef, setDraggableRef] =
createSignal<HTMLElement | null>(null);
const [currentAxis, setCurrentAxis] = createSignal<'x' | 'y'>('x');
const axisCompartment = createCompartment(() =>
axis(currentAxis()),
);
useDraggable(draggableRef, [axisCompartment]);
return (
<div>
<div ref={setDraggableRef}>Current axis: {currentAxis()}</div>
<button
onClick={() =>
setCurrentAxis(currentAxis() === 'x' ? 'y' : 'x')
}
>
Switch Axis
</button>
</div>
);
};
Multiple reactive compartments
import { createSignal } from 'solid-js';
import {
useDraggable,
axis,
bounds,
BoundsFrom,
grid,
createCompartment,
} from '@neodrag/solid';
export const App: Component = () => {
const [draggableRef, setDraggableRef] =
createSignal<HTMLElement | null>(null);
const [currentAxis, setCurrentAxis] = createSignal<'x' | 'y'>('x');
const [gridSize, setGridSize] = createSignal(20);
const [enableBounds, setEnableBounds] = createSignal(false);
const axisComp = createCompartment(() => axis(currentAxis()));
const gridComp = createCompartment(() =>
grid([gridSize(), gridSize()]),
);
const boundsComp = createCompartment(() =>
enableBounds() ? bounds(BoundsFrom.parent()) : null,
);
useDraggable(draggableRef, [axisComp, gridComp, boundsComp]);
return (
<div>
<div ref={setDraggableRef}>Reactive draggable</div>
<div>
<select
value={currentAxis()}
onChange={(e) =>
setCurrentAxis(e.target.value as 'x' | 'y')
}
>
<option value="x">X</option>
<option value="y">Y</option>
</select>
<input
type="range"
min="10"
max="50"
value={gridSize()}
onInput={(e) => setGridSize(Number(e.target.value))}
/>
<label>
<input
type="checkbox"
checked={enableBounds()}
onChange={(e) => setEnableBounds(e.target.checked)}
/>
Enable bounds
</label>
</div>
</div>
);
};
Event Handling
import { useDraggable, events } from '@neodrag/solid';
export const App: Component = () => {
const [draggableRef, setDraggableRef] =
createSignal<HTMLElement | null>(null);
useDraggable(draggableRef, [
events({
onDragStart: (data) => console.log('Started:', data.offset),
onDrag: (data) => console.log('Dragging:', data.offset),
onDragEnd: (data) => console.log('Ended:', data.offset),
}),
]);
return (
<div ref={setDraggableRef}>Check console while dragging</div>
);
};
Drag Controls
import { useDraggable, controls, ControlFrom } from '@neodrag/solid';
export const App: Component = () => {
const [draggableRef, setDraggableRef] =
createSignal<HTMLElement | null>(null);
useDraggable(draggableRef, [
controls({
allow: ControlFrom.selector('.drag-handle'),
block: ControlFrom.selector('.no-drag'),
}),
]);
return (
<div ref={setDraggableRef}>
<div class="drag-handle">🔸 Drag from here</div>
<div>Content area</div>
<div class="no-drag">❌ Can't drag from here</div>
</div>
);
};