# Drag-and-Drop Patterns ## Table of Contents - [Sortable Lists](#sortable-lists) - [Grid Layouts](#grid-layouts) - [Nested Containers](#nested-containers) - [Auto-Scrolling](#auto-scrolling) - [Multi-Select Dragging](#multi-select-dragging) ## Sortable Lists ### Vertical List Pattern Most common drag-and-drop pattern for priority ordering, task lists, and sequential content. **Implementation with dnd-kit:** ```tsx import { DndContext, closestCenter, KeyboardSensor, PointerSensor, useSensor, useSensors } from '@dnd-kit/core'; import { arrayMove, SortableContext, sortableKeyboardCoordinates, verticalListSortingStrategy } from '@dnd-kit/sortable'; function VerticalSortableList({ items, onReorder }) { const sensors = useSensors( useSensor(PointerSensor), useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates, }) ); function handleDragEnd(event) { const { active, over } = event; if (active.id !== over.id) { const oldIndex = items.findIndex(item => item.id === active.id); const newIndex = items.findIndex(item => item.id === over.id); onReorder(arrayMove(items, oldIndex, newIndex)); } } return ( {/* Sortable items here */} ); } ``` ### Horizontal List Pattern Used for tab reordering, carousel management, and timeline elements. **Key Differences:** - Use `horizontalListSortingStrategy` instead of vertical - Adjust drag handle positioning (typically on edges) - Consider touch scrolling conflicts on mobile ```tsx import { horizontalListSortingStrategy } from '@dnd-kit/sortable'; // In SortableContext: {/* Horizontal items */} ``` ### Drag Handle Pattern Provides explicit drag affordance while keeping content interactive. ```tsx import { useSortable } from '@dnd-kit/sortable'; import { CSS } from '@dnd-kit/utilities'; function SortableItem({ id, children }) { const { attributes, listeners, setNodeRef, transform, transition, isDragging, } = useSortable({ id }); const style = { transform: CSS.Transform.toString(transform), transition, opacity: isDragging ? 0.5 : 1, }; return (
{children}
); } ``` ## Grid Layouts ### 2D Grid Pattern For dashboard widgets, image galleries, and card layouts. ```tsx import { rectSortingStrategy } from '@dnd-kit/sortable'; function GridLayout({ items, columns = 3 }) { return (
{items.map(item => ( ))}
); } ``` ### Masonry Layout Pattern For Pinterest-style layouts with variable heights. **Considerations:** - Calculate positions dynamically - Handle reflow on drop - Optimize for performance with many items ```tsx // Use custom collision detection for masonry function masonryCollisionDetection(args) { // Custom logic to handle variable heights // Consider item positions and sizes return closestCenter(args); } ``` ## Nested Containers ### Parent-Child Dragging For tree structures, nested lists, and hierarchical content. ```tsx function NestedSortable({ items, depth = 0 }) { const maxDepth = 3; // Prevent infinite nesting return ( {items.map(item => (
{item.children && depth < maxDepth && ( )}
))}
); } ``` ## Auto-Scrolling ### Edge Detection Pattern Automatically scroll when dragging near container edges. ```tsx import { AutoScrollActivator } from '@dnd-kit/auto-scroll'; function ScrollableList() { const autoScrollOptions = { canScroll: (element) => true, threshold: { x: 0.2, // 20% from edge y: 0.2, }, maxSpeed: 10, acceleration: 10, }; return ( {/* Scrollable content */} ); } ``` ### Viewport Scrolling For full-page draggable interfaces. ```tsx // Enable window scrolling during drag const sensors = useSensors( useSensor(PointerSensor, { activationConstraint: { distance: 8, // Prevent accidental drags }, }) ); // Auto-scroll viewport useEffect(() => { if (isDragging) { // Custom scroll logic based on cursor position } }, [isDragging, cursorPosition]); ``` ## Multi-Select Dragging ### Selection State Pattern Allow dragging multiple items simultaneously. ```tsx function MultiSelectDraggable() { const [selectedIds, setSelectedIds] = useState(new Set()); function handleDragStart(event) { const { active } = event; if (!selectedIds.has(active.id)) { // If dragging unselected item, clear selection setSelectedIds(new Set([active.id])); } // Otherwise drag all selected items } function handleDragEnd(event) { const { active, over } = event; if (over && selectedIds.size > 0) { // Move all selected items relative to drop position const itemsToMove = Array.from(selectedIds); // Reorder logic here } } return ( {/* Multi-selectable items */} ); } ``` ### Visual Feedback for Multi-Select ```tsx function MultiSelectItem({ id, isSelected, isDragging }) { const style = { backgroundColor: isSelected ? 'var(--color-primary-100)' : 'white', opacity: isDragging && isSelected ? 0.5 : 1, border: isSelected ? '2px solid var(--color-primary)' : '1px solid var(--color-border)', }; return
{/* Content */}
; } ``` ## Common Patterns Summary ### Pattern Selection Guide | Use Case | Pattern | Key Considerations | |----------|---------|-------------------| | Task lists | Vertical sortable | Drag handles, keyboard nav | | Tabs | Horizontal sortable | Touch scrolling conflicts | | Dashboard | Grid layout | Responsive columns | | File browser | Nested containers | Depth limits | | Kanban | Multi-container | Drop zones, auto-scroll | | Batch operations | Multi-select | Visual selection state | ### Performance Tips 1. **Virtualization**: For lists >100 items 2. **Throttling**: Limit drag event frequency 3. **Memoization**: Prevent unnecessary re-renders 4. **CSS Transforms**: Use transform, not position 5. **Will-change**: Hint browser about animations ### Accessibility Requirements Every pattern must include: - Keyboard navigation support - Screen reader announcements - Focus management - Alternative UI options - Clear visual feedback ### Mobile Considerations - Long press (300ms) to initiate drag - Prevent scroll during drag - Larger touch targets (44px minimum) - Handle gesture conflicts - Test on actual devices