@lilith/ui-dnd
Drag and drop components for React with grid and list support. Uses framer-motion for smooth animations.
Features
- SortableGrid - CSS Grid-based sortable container with automatic reordering
- SortableList - Flex-based sortable list (vertical or horizontal)
- useDraggable - Hook to make any element draggable
- useDroppable - Hook to make any element a drop target
- DndProvider - Context provider for custom drag-and-drop implementations
Installation
pnpm add @lilith/ui-dnd
Peer Dependencies
{
"react": "^18.0.0 || ^19.0.0",
"react-dom": "^18.0.0 || ^19.0.0",
"styled-components": "^6.0.0"
}
Usage
SortableGrid
Perfect for dashboard widgets, image galleries, or any grid-based reorderable content.
import { SortableGrid } from '@lilith/ui-dnd';
const widgets = ['temperature', 'fan_speed', 'power_draw', 'clock_speed'];
function Dashboard() {
const [order, setOrder] = useState(widgets);
return (
<SortableGrid
items={order}
getItemKey={(item) => item}
renderItem={(item, index, isDragging) => (
<WidgetCard
title={item}
style={{ opacity: isDragging ? 0.8 : 1 }}
/>
)}
onReorder={setOrder}
grid={{
columns: 4, // or 'auto' for responsive
gap: 16,
minColumnWidth: 280, // used when columns='auto'
}}
/>
);
}
SortableList
For task lists, navigation items, or any linear sortable content.
import { SortableList } from '@lilith/ui-dnd';
function TaskList() {
const [tasks, setTasks] = useState([
{ id: '1', title: 'Task 1' },
{ id: '2', title: 'Task 2' },
{ id: '3', title: 'Task 3' },
]);
return (
<SortableList
items={tasks}
getItemKey={(task) => task.id}
renderItem={(task, index, isDragging) => (
<TaskCard task={task} />
)}
onReorder={setTasks}
direction="vertical"
gap={8}
/>
);
}
Custom Drag and Drop with Hooks
For more complex scenarios, use the low-level hooks:
import { DndProvider, useDraggable, useDroppable } from '@lilith/ui-dnd';
function DraggableItem({ id, data }) {
const { dragProps, isDragging, setNodeRef } = useDraggable({
id,
type: 'card',
data,
});
return (
<div
ref={setNodeRef}
{...dragProps}
style={{ opacity: isDragging ? 0.5 : 1 }}
>
Drag me!
</div>
);
}
function DropZone({ id, onItemDrop }) {
const { dropProps, isOver, canDrop, setNodeRef } = useDroppable({
id,
acceptTypes: ['card'],
onDrop: (result) => onItemDrop(result.item),
});
return (
<div
ref={setNodeRef}
{...dropProps}
style={{
background: isOver && canDrop ? 'lightgreen' : 'white',
}}
>
Drop here
</div>
);
}
function App() {
return (
<DndProvider onDrop={(result) => console.log('Dropped:', result)}>
<DraggableItem id="item-1" data={{ name: 'Item 1' }} />
<DropZone id="zone-1" onItemDrop={handleDrop} />
</DndProvider>
);
}
API Reference
SortableGrid Props
| Prop |
Type |
Default |
Description |
items |
T[] |
required |
Array of items to render |
getItemKey |
(item: T) => string |
required |
Function to get unique key |
renderItem |
(item: T, index: number, isDragging: boolean) => ReactNode |
required |
Render function |
onReorder |
(items: T[]) => void |
required |
Callback when order changes |
grid |
GridConfig |
{ columns: 4, gap: 16 } |
Grid configuration |
disabled |
boolean |
false |
Disable drag and drop |
className |
string |
- |
Additional CSS class |
GridConfig
| Prop |
Type |
Default |
Description |
columns |
number | 'auto' |
4 |
Number of columns or auto-fill |
gap |
number | string |
16 |
Gap between items (px or theme key) |
minColumnWidth |
number |
280 |
Min column width when columns='auto' |
SortableList Props
| Prop |
Type |
Default |
Description |
items |
T[] |
required |
Array of items to render |
getItemKey |
(item: T) => string |
required |
Function to get unique key |
renderItem |
(item: T, index: number, isDragging: boolean) => ReactNode |
required |
Render function |
onReorder |
(items: T[]) => void |
required |
Callback when order changes |
direction |
'vertical' | 'horizontal' |
'vertical' |
List direction |
gap |
number | string |
8 |
Gap between items |
disabled |
boolean |
false |
Disable drag and drop |
className |
string |
- |
Additional CSS class |
useDraggable Config
| Prop |
Type |
Default |
Description |
id |
string |
required |
Unique identifier |
type |
string |
required |
Item type for filtering |
data |
T |
- |
Custom data payload |
index |
number |
- |
Index in sortable container |
disabled |
boolean |
false |
Disable dragging |
onDragStart |
(item) => void |
- |
Drag start callback |
onDragEnd |
(item, dropped) => void |
- |
Drag end callback |
useDroppable Config
| Prop |
Type |
Default |
Description |
id |
string |
required |
Unique identifier |
acceptTypes |
string[] |
[] |
Accepted item types (empty = all) |
disabled |
boolean |
false |
Disable dropping |
onDragEnter |
(item) => void |
- |
Item enters zone |
onDragLeave |
(item) => void |
- |
Item leaves zone |
onDrop |
(result) => void |
- |
Item dropped |
Types
import type {
Position,
DragItem,
DragState,
DropResult,
GridConfig,
SortableGridProps,
SortableListProps,
} from '@lilith/ui-dnd';
License
MIT