A lightweight React drag-and-drop library with position persistence. Built for any JS metaframework (Next.js, Remix, Gatsby, etc.).
npm install @thepaulin/d_uimport { DraggableUI, Draggable } from '@thepaulin/d_u';
function App() {
return (
<DraggableUI>
<Draggable id="card-1" defaultPosition={{ x: 100, y: 200 }}>
<div>Drag me</div>
</Draggable>
</DraggableUI>
);
}Persistence and drag context are configured via DraggableUI. Drop-in Draggable makes any child element movable.
- Position persistence — positions saved to
localStorageby default; survives page reloads - Custom storage — supply your own
StorageAdapterfor server/DB-backed persistence - Two positioning modes —
transform(no layout shift) orabsolute - Axis constraint — restrict dragging to
x,y, orboth - Grid snapping — snap to any
[column, row]grid - Drag handles — restrict drag start to a specific handle element
- Full event callbacks —
onDragStart,onDrag,onDragEnd - Headless hook —
useDraggablefor completely custom rendering - Imperative control —
usePositionto read or update positions outside of drag - SSR-safe — no
windowreferences on the server - Zero runtime deps — only peer dependency on React 16.8+
Root provider. Must wrap all Draggable instances.
| Prop | Type | Default | Description |
|---|---|---|---|
storageKey |
string |
'default' |
Key for persisting positions |
storageAdapter |
StorageAdapter |
localStorage adapter |
Custom persistence backend |
positioningMode |
'transform' | 'absolute' |
'transform' |
CSS strategy for positioning |
className |
string |
— | Class for the wrapper element |
style |
CSSProperties |
— | Inline styles for the wrapper |
as |
ElementType |
'div' |
Wrapper element tag |
Makes its single child element draggable.
| Prop | Type | Default | Description |
|---|---|---|---|
id |
string |
required | Unique identifier |
defaultPosition |
{ x: number, y: number } |
{ x: 0, y: 0 } |
Initial position |
disabled |
boolean |
false |
Disables dragging |
axis |
'both' | 'x' | 'y' |
'both' |
Restrict movement axis |
grid |
[number, number] |
— | Snap grid [column, row] |
zIndex |
number |
— | Z-index while dragging (via style) |
handle |
RefObject<HTMLElement> |
— | Element that must be clicked to drag |
onDragStart |
(event) => void |
— | Called on drag start |
onDrag |
(event) => void |
— | Called on each drag move |
onDragEnd |
(event) => void |
— | Called on drag end |
Headless hook — returns { ref, style, onPointerDown, attributes } for fully custom rendering.
Read and update a draggable's position imperatively. Returns { x, y, setPosition, resetPosition }.
Access the nearest DraggableUI context. Throws if called outside a provider.
| Export | Description |
|---|---|
Position |
{ x: number, y: number } |
StorageAdapter |
{ getItem, setItem, removeItem } |
PositioningMode |
'transform' | 'absolute' |
DragStartEvent |
Event payload on drag start |
DragMoveEvent |
Event payload during drag |
DragEndEvent |
Event payload on drag end |
git clone https://github.com/thepaulin/d_ui.git
cd d_ui
npm install| Command | Description |
|---|---|
npm run dev |
Watch mode rebuild |
npm run build |
Production build via tsup |
npm test |
Run test suite (Vitest) |
npm run test:watch |
Watch mode tests |
npm run test:coverage |
Test coverage report |
npm run typecheck |
TypeScript type check |
src/
components/ — DraggableUI, Draggable
context/ — DragContext provider
hooks/ — useDraggable, usePosition, useDragContext
utils/ — storage, coordinates, guards
types/ — TypeScript interfaces
constants.ts — shared constants
- Fork the repo and create a feature branch.
- Run
npm installandnpm run devto start the build watcher. - Write or update tests under
src/**/__tests__/. - Run
npm testandnpm run typecheck— all must pass. - Open a pull request.
The library aims to be minimal. New features should justify their weight. When in doubt, open an issue first to discuss.