Try Live
Add Docs
Rankings
Pricing
Docs
Install
Install
Docs
Pricing
More...
More...
Try Live
Rankings
Enterprise
Create API Key
Add Docs
Dnd
https://github.com/hello-pangea/dnd
Admin
Beautiful and accessible drag and drop for lists with React, offering natural movement, powerful
...
Tokens:
53,553
Snippets:
406
Trust Score:
8.8
Update:
1 week ago
Context
Skills
Chat
Benchmark
89.3
Suggestions
Latest
Show doc for...
Code
Info
Show Results
Context Summary (auto-generated)
Raw
Copy
Link
# @hello-pangea/dnd @hello-pangea/dnd is a beautiful and accessible drag and drop library for React applications. It provides a powerful, natural drag and drop experience for reorderable lists with support for vertical lists, horizontal lists, movement between lists, virtual lists for handling large datasets, and combining items. The library offers excellent accessibility with keyboard and screen reader support out of the box. The library is built around three core components: `DragDropContext` which wraps your application to enable drag and drop, `Droppable` which defines areas where items can be dropped, and `Draggable` which wraps items that can be dragged. It supports mouse, touch, and keyboard interactions through its sensor API, and provides lifecycle responders (onDragStart, onDragUpdate, onDragEnd) for handling state updates and custom behaviors during drag operations. ## Installation Install the package using your preferred package manager. ```bash # npm npm install @hello-pangea/dnd --save # pnpm pnpm add @hello-pangea/dnd # yarn yarn add @hello-pangea/dnd ``` ## DragDropContext The `DragDropContext` component wraps the part of your application where you want drag and drop enabled. It requires an `onDragEnd` responder and accepts optional responders for other lifecycle events. You should typically wrap your entire application in a single `DragDropContext`. ```tsx import React, { useState, useCallback } from 'react'; import { DragDropContext, DropResult } from '@hello-pangea/dnd'; interface Item { id: string; content: string; } function App() { const [items, setItems] = useState<Item[]>([ { id: 'item-1', content: 'First item' }, { id: 'item-2', content: 'Second item' }, { id: 'item-3', content: 'Third item' }, ]); const onDragStart = useCallback(() => { // Optional: Add visual feedback when drag starts console.log('Drag started'); }, []); const onDragUpdate = useCallback(() => { // Optional: React to position changes during drag console.log('Drag updated'); }, []); const onDragEnd = useCallback((result: DropResult) => { // Required: Handle the drop const { destination, source } = result; // Dropped outside a droppable area if (!destination) { return; } // Dropped in the same position if ( destination.droppableId === source.droppableId && destination.index === source.index ) { return; } // Reorder the items const newItems = Array.from(items); const [removed] = newItems.splice(source.index, 1); newItems.splice(destination.index, 0, removed); setItems(newItems); }, [items]); return ( <DragDropContext onDragStart={onDragStart} onDragUpdate={onDragUpdate} onDragEnd={onDragEnd} > {/* Droppable and Draggable components go here */} </DragDropContext> ); } ``` ## Droppable The `Droppable` component creates an area where `Draggable` items can be dropped. It uses the render props pattern, providing `provided` and `snapshot` arguments to its children function. The `provided.innerRef` must be bound to the DOM element, and `provided.placeholder` must be included to reserve space during dragging. ```tsx import React from 'react'; import { Droppable, DroppableProvided, DroppableStateSnapshot } from '@hello-pangea/dnd'; interface Item { id: string; content: string; } interface ListProps { items: Item[]; } function DroppableList({ items }: ListProps) { return ( <Droppable droppableId="my-list" type="ITEM" direction="vertical"> {(provided: DroppableProvided, snapshot: DroppableStateSnapshot) => ( <div ref={provided.innerRef} {...provided.droppableProps} style={{ backgroundColor: snapshot.isDraggingOver ? 'lightblue' : 'lightgrey', padding: 8, minHeight: 200, }} > {items.map((item, index) => ( <DraggableItem key={item.id} item={item} index={index} /> ))} {provided.placeholder} </div> )} </Droppable> ); } // Droppable with horizontal direction function HorizontalList({ items }: ListProps) { return ( <Droppable droppableId="horizontal-list" direction="horizontal"> {(provided, snapshot) => ( <div ref={provided.innerRef} {...provided.droppableProps} style={{ display: 'flex', flexDirection: 'row', backgroundColor: snapshot.isDraggingOver ? 'lightblue' : 'lightgrey', padding: 8, }} > {items.map((item, index) => ( <DraggableItem key={item.id} item={item} index={index} /> ))} {provided.placeholder} </div> )} </Droppable> ); } // Droppable with conditional dropping disabled function ConditionalDroppable({ items, isDropDisabled }: ListProps & { isDropDisabled: boolean }) { return ( <Droppable droppableId="conditional-list" isDropDisabled={isDropDisabled}> {(provided, snapshot) => ( <div ref={provided.innerRef} {...provided.droppableProps} style={{ backgroundColor: isDropDisabled ? 'lightcoral' : snapshot.isDraggingOver ? 'lightgreen' : 'lightgrey', padding: 8, minHeight: 200, }} > {items.map((item, index) => ( <DraggableItem key={item.id} item={item} index={index} /> ))} {provided.placeholder} </div> )} </Droppable> ); } ``` ## Draggable The `Draggable` component wraps items that can be dragged. It requires a unique `draggableId` and an `index`. Like `Droppable`, it uses render props providing `provided` (with `innerRef`, `draggableProps`, and `dragHandleProps`) and `snapshot` for drag state information. ```tsx import React from 'react'; import { Draggable, DraggableProvided, DraggableStateSnapshot, DraggableStyle, } from '@hello-pangea/dnd'; interface Item { id: string; content: string; } interface DraggableItemProps { item: Item; index: number; } // Basic draggable item function DraggableItem({ item, index }: DraggableItemProps) { return ( <Draggable draggableId={item.id} index={index}> {(provided: DraggableProvided, snapshot: DraggableStateSnapshot) => ( <div ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps} style={{ userSelect: 'none', padding: 16, margin: '0 0 8px 0', backgroundColor: snapshot.isDragging ? 'lightgreen' : 'white', boxShadow: snapshot.isDragging ? '0 5px 10px rgba(0,0,0,0.2)' : 'none', ...provided.draggableProps.style, }} > {item.content} </div> )} </Draggable> ); } // Draggable with separate drag handle function DraggableWithHandle({ item, index }: DraggableItemProps) { return ( <Draggable draggableId={item.id} index={index}> {(provided, snapshot) => ( <div ref={provided.innerRef} {...provided.draggableProps} style={{ display: 'flex', alignItems: 'center', padding: 16, margin: '0 0 8px 0', backgroundColor: snapshot.isDragging ? 'lightgreen' : 'white', ...provided.draggableProps.style, }} > {/* Only this element triggers dragging */} <span {...provided.dragHandleProps} style={{ marginRight: 8, cursor: 'grab' }} > ⠿ </span> <span>{item.content}</span> </div> )} </Draggable> ); } // Conditionally disabled draggable function ConditionalDraggable({ item, index, isDragDisabled }: DraggableItemProps & { isDragDisabled: boolean }) { return ( <Draggable draggableId={item.id} index={index} isDragDisabled={isDragDisabled}> {(provided, snapshot) => ( <div ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps} style={{ padding: 16, margin: '0 0 8px 0', backgroundColor: isDragDisabled ? 'lightgrey' : snapshot.isDragging ? 'lightgreen' : 'white', opacity: isDragDisabled ? 0.5 : 1, ...provided.draggableProps.style, }} > {item.content} </div> )} </Draggable> ); } ``` ## Complete Simple List Example A complete working example showing a reorderable vertical list with all three core components working together. ```tsx import React, { useState } from 'react'; import { DragDropContext, Droppable, Draggable, DropResult, DraggableStyle, } from '@hello-pangea/dnd'; interface Item { id: string; content: string; } // Helper function to reorder items const reorder = (list: Item[], startIndex: number, endIndex: number): Item[] => { const result = Array.from(list); const [removed] = result.splice(startIndex, 1); result.splice(endIndex, 0, removed); return result; }; // Generate sample items const getItems = (count: number): Item[] => Array.from({ length: count }, (_, k) => ({ id: `item-${k}`, content: `Item ${k + 1}`, })); const getItemStyle = (isDragging: boolean, draggableStyle?: DraggableStyle) => ({ userSelect: 'none' as const, padding: 16, margin: '0 0 8px 0', background: isDragging ? 'lightgreen' : 'white', border: '1px solid #ddd', borderRadius: 4, ...draggableStyle, }); const getListStyle = (isDraggingOver: boolean) => ({ background: isDraggingOver ? 'lightblue' : '#f4f4f4', padding: 8, width: 300, minHeight: 400, }); function SimpleList() { const [items, setItems] = useState<Item[]>(getItems(10)); const onDragEnd = (result: DropResult) => { if (!result.destination) { return; } const reorderedItems = reorder( items, result.source.index, result.destination.index ); setItems(reorderedItems); }; return ( <DragDropContext onDragEnd={onDragEnd}> <Droppable droppableId="droppable"> {(provided, snapshot) => ( <div ref={provided.innerRef} style={getListStyle(snapshot.isDraggingOver)} {...provided.droppableProps} > {items.map((item, index) => ( <Draggable key={item.id} draggableId={item.id} index={index}> {(provided, snapshot) => ( <div ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps} style={getItemStyle( snapshot.isDragging, provided.draggableProps.style )} > {item.content} </div> )} </Draggable> ))} {provided.placeholder} </div> )} </Droppable> </DragDropContext> ); } export default SimpleList; ``` ## Multiple Lists (Kanban Board) Implementing drag and drop between multiple lists, commonly used for Kanban boards or task management applications. ```tsx import React, { useState } from 'react'; import { DragDropContext, Droppable, Draggable, DropResult, DraggableLocation, } from '@hello-pangea/dnd'; interface Task { id: string; content: string; } interface Column { id: string; title: string; tasks: Task[]; } interface BoardState { columns: { [key: string]: Column }; columnOrder: string[]; } const initialData: BoardState = { columns: { 'column-1': { id: 'column-1', title: 'To Do', tasks: [ { id: 'task-1', content: 'Create design mockups' }, { id: 'task-2', content: 'Review requirements' }, ], }, 'column-2': { id: 'column-2', title: 'In Progress', tasks: [ { id: 'task-3', content: 'Implement drag and drop' }, ], }, 'column-3': { id: 'column-3', title: 'Done', tasks: [ { id: 'task-4', content: 'Set up project' }, ], }, }, columnOrder: ['column-1', 'column-2', 'column-3'], }; function KanbanBoard() { const [state, setState] = useState<BoardState>(initialData); const onDragEnd = (result: DropResult) => { const { destination, source, draggableId, type } = result; if (!destination) return; if ( destination.droppableId === source.droppableId && destination.index === source.index ) { return; } // Moving columns if (type === 'COLUMN') { const newColumnOrder = Array.from(state.columnOrder); newColumnOrder.splice(source.index, 1); newColumnOrder.splice(destination.index, 0, draggableId); setState({ ...state, columnOrder: newColumnOrder, }); return; } // Moving tasks const sourceColumn = state.columns[source.droppableId]; const destColumn = state.columns[destination.droppableId]; if (sourceColumn === destColumn) { // Reordering within the same column const newTasks = Array.from(sourceColumn.tasks); const [removed] = newTasks.splice(source.index, 1); newTasks.splice(destination.index, 0, removed); const newColumn = { ...sourceColumn, tasks: newTasks }; setState({ ...state, columns: { ...state.columns, [newColumn.id]: newColumn }, }); } else { // Moving to a different column const sourceTasks = Array.from(sourceColumn.tasks); const [removed] = sourceTasks.splice(source.index, 1); const destTasks = Array.from(destColumn.tasks); destTasks.splice(destination.index, 0, removed); setState({ ...state, columns: { ...state.columns, [sourceColumn.id]: { ...sourceColumn, tasks: sourceTasks }, [destColumn.id]: { ...destColumn, tasks: destTasks }, }, }); } }; return ( <DragDropContext onDragEnd={onDragEnd}> <Droppable droppableId="board" type="COLUMN" direction="horizontal"> {(provided) => ( <div ref={provided.innerRef} {...provided.droppableProps} style={{ display: 'flex', gap: 16, padding: 16 }} > {state.columnOrder.map((columnId, index) => { const column = state.columns[columnId]; return ( <Draggable key={column.id} draggableId={column.id} index={index}> {(provided) => ( <div ref={provided.innerRef} {...provided.draggableProps} style={{ width: 280, backgroundColor: '#f4f5f7', borderRadius: 4, ...provided.draggableProps.style, }} > <h3 {...provided.dragHandleProps} style={{ padding: '8px 16px', margin: 0, cursor: 'grab' }} > {column.title} </h3> <Droppable droppableId={column.id} type="TASK"> {(provided, snapshot) => ( <div ref={provided.innerRef} {...provided.droppableProps} style={{ padding: 8, minHeight: 200, backgroundColor: snapshot.isDraggingOver ? '#e3fcef' : 'transparent', }} > {column.tasks.map((task, index) => ( <Draggable key={task.id} draggableId={task.id} index={index} > {(provided, snapshot) => ( <div ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps} style={{ padding: 12, marginBottom: 8, backgroundColor: snapshot.isDragging ? '#e3fcef' : 'white', borderRadius: 4, boxShadow: snapshot.isDragging ? '0 5px 10px rgba(0,0,0,0.15)' : '0 1px 2px rgba(0,0,0,0.1)', ...provided.draggableProps.style, }} > {task.content} </div> )} </Draggable> ))} {provided.placeholder} </div> )} </Droppable> </div> )} </Draggable> ); })} {provided.placeholder} </div> )} </Droppable> </DragDropContext> ); } export default KanbanBoard; ``` ## Combining Items Enable combining items by setting `isCombineEnabled` on a Droppable. When items are combined, the `result.combine` property in `onDragEnd` will contain information about the combination. ```tsx import React, { useState } from 'react'; import { DragDropContext, Droppable, Draggable, DropResult } from '@hello-pangea/dnd'; interface Item { id: string; content: string; children?: Item[]; } function CombiningList() { const [items, setItems] = useState<Item[]>([ { id: 'item-1', content: 'Folder 1', children: [] }, { id: 'item-2', content: 'File A' }, { id: 'item-3', content: 'File B' }, { id: 'item-4', content: 'File C' }, ]); const onDragEnd = (result: DropResult) => { const { source, destination, combine } = result; // Handle combining if (combine) { const newItems = [...items]; const [removed] = newItems.splice(source.index, 1); // Find the target item and add the dragged item as a child const targetIndex = newItems.findIndex(item => item.id === combine.draggableId); if (targetIndex !== -1) { const target = newItems[targetIndex]; newItems[targetIndex] = { ...target, children: [...(target.children || []), removed], }; } setItems(newItems); return; } // Handle normal reordering if (!destination) return; const newItems = Array.from(items); const [removed] = newItems.splice(source.index, 1); newItems.splice(destination.index, 0, removed); setItems(newItems); }; return ( <DragDropContext onDragEnd={onDragEnd}> <Droppable droppableId="list" isCombineEnabled> {(provided, snapshot) => ( <div ref={provided.innerRef} {...provided.droppableProps} style={{ padding: 8, width: 300, backgroundColor: snapshot.isDraggingOver ? '#e3fcef' : '#f4f4f4', }} > {items.map((item, index) => ( <Draggable key={item.id} draggableId={item.id} index={index}> {(provided, snapshot) => ( <div ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps} style={{ padding: 16, marginBottom: 8, backgroundColor: snapshot.combineTargetFor ? '#bbdefb' : snapshot.isDragging ? '#c8e6c9' : 'white', border: snapshot.combineTargetFor ? '2px dashed #1976d2' : '1px solid #ddd', borderRadius: 4, ...provided.draggableProps.style, }} > {item.content} {item.children && item.children.length > 0 && ( <div style={{ marginTop: 8, paddingLeft: 16, fontSize: 12 }}> Contains: {item.children.map(c => c.content).join(', ')} </div> )} </div> )} </Draggable> ))} {provided.placeholder} </div> )} </Droppable> </DragDropContext> ); } export default CombiningList; ``` ## Virtual Lists For large lists (500+ items), use virtual lists with the `mode="virtual"` prop and `renderClone` API for optimal performance. ```tsx import React, { useState } from 'react'; import { FixedSizeList, ListChildComponentProps } from 'react-window'; import { DragDropContext, Droppable, Draggable, DropResult, DraggableProvided, DraggableStateSnapshot, DraggableRubric, DroppableProvided, DroppableStateSnapshot, } from '@hello-pangea/dnd'; interface Item { id: string; content: string; } const generateItems = (count: number): Item[] => Array.from({ length: count }, (_, k) => ({ id: `item-${k}`, content: `Item ${k + 1}`, })); interface RowProps extends ListChildComponentProps { data: Item[]; } function Row({ data, index, style }: RowProps) { const item = data[index]; // Extra item for placeholder space if (!item) { return null; } return ( <Draggable draggableId={item.id} index={index} key={item.id}> {(provided: DraggableProvided, snapshot: DraggableStateSnapshot) => ( <div ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps} style={{ ...style, ...provided.draggableProps.style, padding: 16, backgroundColor: snapshot.isDragging ? '#c8e6c9' : 'white', borderBottom: '1px solid #eee', }} > {item.content} </div> )} </Draggable> ); } function VirtualList() { const [items, setItems] = useState<Item[]>(generateItems(1000)); const onDragEnd = (result: DropResult) => { if (!result.destination) return; const newItems = Array.from(items); const [removed] = newItems.splice(result.source.index, 1); newItems.splice(result.destination.index, 0, removed); setItems(newItems); }; return ( <DragDropContext onDragEnd={onDragEnd}> <Droppable droppableId="virtual-list" mode="virtual" renderClone={( provided: DraggableProvided, snapshot: DraggableStateSnapshot, rubric: DraggableRubric ) => ( <div ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps} style={{ ...provided.draggableProps.style, padding: 16, backgroundColor: '#c8e6c9', boxShadow: '0 5px 10px rgba(0,0,0,0.2)', }} > {items[rubric.source.index].content} </div> )} > {(provided: DroppableProvided, snapshot: DroppableStateSnapshot) => { const itemCount = snapshot.isUsingPlaceholder ? items.length + 1 : items.length; return ( <FixedSizeList height={500} itemCount={itemCount} itemSize={50} width={300} outerRef={provided.innerRef} itemData={items} > {Row} </FixedSizeList> ); }} </Droppable> </DragDropContext> ); } export default VirtualList; ``` ## Custom Sensors (Programmatic Drag) Create custom sensors to control dragging programmatically or with custom input methods. ```tsx import React, { useEffect, useCallback } from 'react'; import { DragDropContext, Droppable, Draggable, SensorAPI, PreDragActions, SnapDragActions, DropResult, } from '@hello-pangea/dnd'; // Custom sensor that allows keyboard arrow keys to trigger moves function useArrowKeySensor(api: SensorAPI) { const onKeyDown = useCallback( (event: KeyboardEvent) => { if (event.key !== 'ArrowUp' && event.key !== 'ArrowDown') { return; } // Find the first draggable const draggableId = api.findClosestDraggableId(event); if (!draggableId) return; // Check if we can start a drag if (!api.canGetLock(draggableId)) return; event.preventDefault(); const preDrag: PreDragActions | null = api.tryGetLock(draggableId); if (!preDrag) return; const drag: SnapDragActions = preDrag.snapLift(); // Move based on arrow key if (event.key === 'ArrowUp') { drag.moveUp(); } else { drag.moveDown(); } // Drop after a short delay setTimeout(() => { if (drag.isActive()) { drag.drop(); } }, 100); }, [api] ); useEffect(() => { window.addEventListener('keydown', onKeyDown); return () => window.removeEventListener('keydown', onKeyDown); }, [onKeyDown]); } // Programmatic drag sensor for scripted experiences function useScriptedDragSensor(api: SensorAPI) { useEffect(() => { // Example: Automatically demonstrate a drag after component mounts const timeout = setTimeout(() => { const preDrag = api.tryGetLock('item-0'); if (!preDrag) return; const drag = preDrag.snapLift(); // Perform a sequence of moves const moves = [ () => drag.moveDown(), () => drag.moveDown(), () => drag.moveDown(), () => drag.drop(), ]; moves.forEach((move, index) => { setTimeout(() => { if (drag.isActive()) { move(); } }, (index + 1) * 300); }); }, 2000); return () => clearTimeout(timeout); }, [api]); } function CustomSensorDemo() { const [items, setItems] = React.useState([ { id: 'item-0', content: 'Item 1' }, { id: 'item-1', content: 'Item 2' }, { id: 'item-2', content: 'Item 3' }, { id: 'item-3', content: 'Item 4' }, ]); const onDragEnd = (result: DropResult) => { if (!result.destination) return; const newItems = Array.from(items); const [removed] = newItems.splice(result.source.index, 1); newItems.splice(result.destination.index, 0, removed); setItems(newItems); }; return ( <DragDropContext onDragEnd={onDragEnd} sensors={[useArrowKeySensor, useScriptedDragSensor]} enableDefaultSensors={true} > <Droppable droppableId="list"> {(provided) => ( <div ref={provided.innerRef} {...provided.droppableProps} style={{ padding: 8, width: 300 }} > {items.map((item, index) => ( <Draggable key={item.id} draggableId={item.id} index={index}> {(provided, snapshot) => ( <div ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps} style={{ padding: 16, marginBottom: 8, backgroundColor: snapshot.isDragging ? '#c8e6c9' : 'white', border: '1px solid #ddd', ...provided.draggableProps.style, }} > {item.content} </div> )} </Draggable> ))} {provided.placeholder} </div> )} </Droppable> </DragDropContext> ); } export default CustomSensorDemo; ``` ## Responders (Lifecycle Events) The `DragDropContext` provides responders to handle different stages of the drag lifecycle. ```tsx import React, { useState } from 'react'; import { DragDropContext, Droppable, Draggable, BeforeCapture, DragStart, DragUpdate, DropResult, ResponderProvided, } from '@hello-pangea/dnd'; interface Item { id: string; content: string; } function ResponderDemo() { const [items, setItems] = useState<Item[]>([ { id: 'item-1', content: 'Item 1' }, { id: 'item-2', content: 'Item 2' }, { id: 'item-3', content: 'Item 3' }, ]); const [dragStatus, setDragStatus] = useState<string>('Idle'); // Called before dimensions are captured - can add/remove elements here const onBeforeCapture = (before: BeforeCapture) => { console.log('Before capture:', before.draggableId); setDragStatus(`Preparing to drag: ${before.draggableId}`); }; // Called after dimensions captured but before drag starts visually const onBeforeDragStart = (start: DragStart) => { console.log('Before drag start:', start); setDragStatus(`About to drag: ${start.draggableId} from index ${start.source.index}`); }; // Called when drag starts const onDragStart = (start: DragStart, provided: ResponderProvided) => { console.log('Drag started:', start); setDragStatus(`Dragging: ${start.draggableId}`); // Custom screen reader announcement provided.announce(`You have lifted item ${start.draggableId}`); }; // Called when something changes during drag const onDragUpdate = (update: DragUpdate, provided: ResponderProvided) => { console.log('Drag update:', update); if (update.destination) { setDragStatus( `Moving to: ${update.destination.droppableId} at index ${update.destination.index}` ); provided.announce( `You are currently at position ${update.destination.index + 1}` ); } else { setDragStatus('Currently not over a droppable area'); provided.announce('You are currently not over a droppable area'); } }; // Called when drag ends - REQUIRED const onDragEnd = (result: DropResult, provided: ResponderProvided) => { console.log('Drag ended:', result); const { source, destination, reason } = result; if (reason === 'CANCEL') { setDragStatus('Drag cancelled'); provided.announce('Drag cancelled'); return; } if (!destination) { setDragStatus('Dropped outside'); provided.announce('Item dropped outside of any list'); return; } if ( source.droppableId === destination.droppableId && source.index === destination.index ) { setDragStatus('No change'); provided.announce('Item returned to original position'); return; } // Reorder items const newItems = Array.from(items); const [removed] = newItems.splice(source.index, 1); newItems.splice(destination.index, 0, removed); setItems(newItems); setDragStatus(`Moved from position ${source.index + 1} to ${destination.index + 1}`); provided.announce( `Item moved from position ${source.index + 1} to position ${destination.index + 1}` ); }; return ( <div> <div style={{ marginBottom: 16, padding: 8, backgroundColor: '#f0f0f0' }}> Status: {dragStatus} </div> <DragDropContext onBeforeCapture={onBeforeCapture} onBeforeDragStart={onBeforeDragStart} onDragStart={onDragStart} onDragUpdate={onDragUpdate} onDragEnd={onDragEnd} > <Droppable droppableId="list"> {(provided) => ( <div ref={provided.innerRef} {...provided.droppableProps} style={{ padding: 8, width: 300 }} > {items.map((item, index) => ( <Draggable key={item.id} draggableId={item.id} index={index}> {(provided, snapshot) => ( <div ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps} style={{ padding: 16, marginBottom: 8, backgroundColor: snapshot.isDragging ? '#c8e6c9' : 'white', border: '1px solid #ddd', ...provided.draggableProps.style, }} > {item.content} </div> )} </Draggable> ))} {provided.placeholder} </div> )} </Droppable> </DragDropContext> </div> ); } export default ResponderDemo; ``` ## TypeScript Types Key TypeScript types exported by the library for use in your applications. ```tsx import type { // Core component props DragDropContextProps, DroppableProps, DraggableProps, // Provided objects (from render props) DroppableProvided, DroppableProvidedProps, DraggableProvided, DraggableProvidedDraggableProps, DraggableProvidedDragHandleProps, // Snapshot objects (drag state) DroppableStateSnapshot, DraggableStateSnapshot, // Result and location types DropResult, DragStart, DragUpdate, BeforeCapture, DraggableLocation, DraggableRubric, DropAnimation, // ID types DraggableId, DroppableId, TypeId, Id, // Movement and mode types MovementMode, Direction, // Responder types OnBeforeCaptureResponder, OnBeforeDragStartResponder, OnDragStartResponder, OnDragUpdateResponder, OnDragEndResponder, ResponderProvided, Announce, // Sensor types Sensor, SensorAPI, PreDragActions, FluidDragActions, SnapDragActions, TryGetLock, TryGetLockOptions, // Style types DraggableStyle, DraggingStyle, NotDraggingStyle, } from '@hello-pangea/dnd'; // Example usage with explicit typing const handleDragEnd: OnDragEndResponder = ( result: DropResult, provided: ResponderProvided ) => { const { source, destination, draggableId, type, reason, combine } = result; if (!destination) { provided.announce('Item dropped outside'); return; } const sourceLocation: DraggableLocation = source; const destLocation: DraggableLocation = destination; console.log(`Moved ${draggableId} from ${sourceLocation.index} to ${destLocation.index}`); }; ``` ## Summary @hello-pangea/dnd is ideal for building sortable lists, Kanban boards, task managers, and any interface requiring drag and drop reordering. The library excels at creating accessible drag and drop experiences with built-in keyboard navigation and screen reader support. Common use cases include todo list applications, project management boards, file organizers, and any UI where users need to reorder items visually. Integration follows a consistent pattern: wrap your app in `DragDropContext` with responder callbacks, define drop zones with `Droppable` components, and make items draggable with `Draggable` components. For large datasets, use virtual lists with `mode="virtual"` and `renderClone`. Custom behaviors can be implemented through the sensor API for programmatic control or alternative input methods. The library integrates well with state management solutions like Redux or React Context, and styling can be handled through any CSS approach including CSS-in-JS libraries like styled-components or Emotion.