dnd-kit-implementation
dnd-kit Implementation Guide
Overview
This skill provides patterns for implementing drag-and-drop functionality using dnd-kit library that supports both sortable containers and droppable targets simultaneously.
Core Concept
The key to combining useSortable and useDroppable is conditional logic based on drag context:
- When dragging items → containers act as drop-only targets
- When dragging containers → containers act as sortable elements
This is achieved by detecting what's currently being dragged and enabling only the appropriate functionality.
Implementation Pattern
Container Component Structure
const SortableDroppableContainer = ({ container }) => {
// useSortable for container reordering
const {
attributes,
listeners,
setNodeRef: setSortableRef,
transform,
transition,
isDragging,
} = useSortable({ id: container.id });
// useDroppable for receiving items
const { setNodeRef: setDroppableRef, isOver } = useDroppable({
id: container.id,
});
// Active element from context
const { active } = useDndContext();
// Determine behavior based on what's being dragged
const isItem = active?.id?.toString().startsWith('item-');
const isContainer = active?.id?.toString().startsWith('container-');
// Apply conditional refs
const setNodeRef = isItem ? setDroppableRef : setSortableRef;
return (
<div ref={setNodeRef} {...(isContainer ? attributes : {})} style={style}>
<div {...(isContainer ? listeners : {})}>{/* Drag handle */}</div>
{/* Container content */}
</div>
);
};
Key Implementation Points
- Dual Hook Usage: Use both
useSortableanduseDroppablein the same component - Context Detection: Use
useDndContext()to check what's being dragged - Conditional Refs: Apply the appropriate ref based on drag state
- ID Naming Convention: Use prefixes like
container-*anditem-*to distinguish types
ID Naming Convention
// Containers
const containerId = `container-${id}`;
// Items
const itemId = `item-${id}`;
This convention makes conditional logic clean and maintainable.
State Management Critical Point
When updating container items, always create new objects:
// ❌ Won't trigger re-render
containers.items.push(newItem);
// ✅ Correct - creates new reference
setContainers(prev => prev.map(c =>
c.id === targetId
? { ...c, items: [...c.items, newItem] }
: c
));
Common Use Cases
- Kanban Boards: Reorder columns and move cards between columns
- File Management: Reorganize folders and move files into folders
- Playlist Editors: Reorder playlists and add songs to playlists
- Task Managers: Reorder task lists and move tasks between lists
References
For detailed implementation examples and code patterns, see:
- implementation-patterns.md - Complete component examples with TypeScript
- state-management.md - State update patterns and common pitfalls
More from atman-33/skills
openspec-archive-change
Archive a completed change in the experimental workflow. Use when the user wants to finalize and archive a change after implementation is complete.
1global-prompts-sync
Synchronizes prompt files (agents, rules, commands) from the skill's prompts directory to both GitHub Copilot and OpenCode global configuration directories. Use when the user wants to update their AI editor prompt libraries.
1react-router-file-routes
Reference guide for React Router v7 file-based routing conventions. Use when creating or organizing routes in React Router v7 projects, naming route files, or working with dynamic segments, nested routes, pathless layouts, optional segments, splat routes, and special characters in file-based routing.
1oh-my-opencode-model-switcher
Switch oh-my-opencode.json model configurations between different modes using YAML-based mapping definitions
1