Skip to main content

Overview

This guide walks you through creating your first drag and drop interface with dnd-kit. You’ll learn the fundamental concepts by building a simple draggable item, then progress to creating a sortable list.
This guide uses React examples. For other frameworks, the concepts are identical—only the syntax differs slightly.

Prerequisites

Before starting, make sure you have:
1

Installed dnd-kit

Follow the installation guide to install the necessary packages for your framework.
2

Basic framework knowledge

Familiarity with React hooks (or equivalent in your framework) is helpful.

Your first draggable

Let’s start with the simplest possible example: a single draggable element.
1

Set up the provider

Wrap your application (or the part that needs drag and drop) with DragDropProvider:
App.tsx
import {DragDropProvider} from '@dnd-kit/react';

function App() {
  return (
    <DragDropProvider>
      {/* Your draggable elements will go here */}
    </DragDropProvider>
  );
}

export default App;
The DragDropProvider manages the drag and drop state and coordinates all draggable and droppable elements.
2

Create a draggable component

Use the useDraggable hook to make an element draggable:
Draggable.tsx
import {useState} from 'react';
import {useDraggable} from '@dnd-kit/react';

export function Draggable() {
  const [element, setElement] = useState<Element | null>(null);
  
  const {isDragging} = useDraggable({
    id: 'draggable-1',
    element,
  });

  return (
    <div
      ref={setElement}
      style={{
        padding: '20px',
        border: '2px solid #666',
        borderRadius: '8px',
        backgroundColor: isDragging ? '#e3f2fd' : '#fff',
        cursor: isDragging ? 'grabbing' : 'grab',
        opacity: isDragging ? 0.5 : 1,
      }}
    >
      Drag me!
    </div>
  );
}
Key points:
  • Each draggable needs a unique id
  • Pass the element reference via the element prop
  • Use isDragging to provide visual feedback
3

Use your draggable

Import and render your draggable component:
App.tsx
import {DragDropProvider} from '@dnd-kit/react';
import {Draggable} from './Draggable';

function App() {
  return (
    <DragDropProvider>
      <div style={{padding: '50px'}}>
        <Draggable />
      </div>
    </DragDropProvider>
  );
}

export default App;
You should now be able to drag the element around! The element will follow your cursor and return to its original position when released.

Adding a drop zone

Now let’s add a droppable area where you can drop the draggable element.
1

Create a droppable component

Use the useDroppable hook:
Droppable.tsx
import {useState} from 'react';
import {useDroppable} from '@dnd-kit/react';

export function Droppable({children}: {children: React.ReactNode}) {
  const [element, setElement] = useState<Element | null>(null);
  
  const {isDropTarget} = useDroppable({
    id: 'droppable-1',
    element,
  });

  return (
    <div
      ref={setElement}
      style={{
        minHeight: '200px',
        padding: '20px',
        border: '2px dashed #999',
        borderRadius: '8px',
        backgroundColor: isDropTarget ? '#e8f5e9' : '#f5f5f5',
        transition: 'background-color 0.2s',
      }}
    >
      {children}
    </div>
  );
}
The isDropTarget property indicates when a draggable is hovering over this droppable.
2

Handle drop events

Add event handlers to track when items are dropped:
App.tsx
import {DragDropProvider} from '@dnd-kit/react';
import type {DragEndEvent} from '@dnd-kit/react';
import {Draggable} from './Draggable';
import {Droppable} from './Droppable';

function App() {
  const handleDragEnd = (event: DragEndEvent) => {
    const {source, target} = event.operation;
    
    if (target) {
      console.log(`Dropped ${source.id} on ${target.id}`);
    }
  };

  return (
    <DragDropProvider onDragEnd={handleDragEnd}>
      <div style={{padding: '50px', display: 'flex', gap: '20px'}}>
        <div>
          <h3>Draggable</h3>
          <Draggable />
        </div>
        <div style={{flex: 1}}>
          <h3>Drop Zone</h3>
          <Droppable>
            Drop here!
          </Droppable>
        </div>
      </div>
    </DragDropProvider>
  );
}

export default App;

Building a sortable list

Now let’s create something more practical: a sortable list where you can reorder items by dragging.
1

Install the helpers package

The helpers package provides utilities for common operations like reordering:
npm install @dnd-kit/helpers @dnd-kit/collision
2

Create a sortable item component

Use the useSortable hook for items in a sortable list:
SortableItem.tsx
import {useState, memo} from 'react';
import {useSortable} from '@dnd-kit/react/sortable';
import {directionBiased} from '@dnd-kit/collision';

interface Props {
  id: string | number;
  index: number;
}

export const SortableItem = memo(function SortableItem({id, index}: Props) {
  const [element, setElement] = useState<Element | null>(null);
  
  const {isDragging} = useSortable({
    id,
    index,
    element,
    collisionDetector: directionBiased,
  });

  return (
    <div
      ref={setElement}
      style={{
        padding: '16px 20px',
        marginBottom: '8px',
        backgroundColor: '#fff',
        border: '1px solid #ddd',
        borderRadius: '8px',
        cursor: 'grab',
        opacity: isDragging ? 0.5 : 1,
        boxShadow: isDragging
          ? '0 10px 20px rgba(0,0,0,0.2)'
          : '0 1px 3px rgba(0,0,0,0.1)',
        transition: 'box-shadow 0.2s',
      }}
    >
      {id}
    </div>
  );
});
Key differences from basic draggables:
  • Uses useSortable instead of useDraggable
  • Requires both id and index props
  • Uses directionBiased collision detection optimized for lists
3

Create the sortable list

Build the list with state management:
SortableList.tsx
import {useState} from 'react';
import {DragDropProvider} from '@dnd-kit/react';
import type {DragEndEvent} from '@dnd-kit/react';
import {move} from '@dnd-kit/helpers';
import {SortableItem} from './SortableItem';

export function SortableList() {
  const [items, setItems] = useState([
    'Item 1',
    'Item 2', 
    'Item 3',
    'Item 4',
    'Item 5',
  ]);

  const handleDragEnd = (event: DragEndEvent) => {
    setItems((currentItems) => move(currentItems, event));
  };

  return (
    <DragDropProvider onDragEnd={handleDragEnd}>
      <div style={{maxWidth: '400px', margin: '0 auto', padding: '20px'}}>
        <h2>Sortable List</h2>
        <div>
          {items.map((item, index) => (
            <SortableItem key={item} id={item} index={index} />
          ))}
        </div>
      </div>
    </DragDropProvider>
  );
}
The move helper automatically calculates the new array order based on the drag event.
You now have a fully functional sortable list! Try dragging items to reorder them.

Adding visual feedback

Enhance the user experience with a drag overlay that follows the cursor:
SortableList.tsx
import {useState} from 'react';
import {DragDropProvider, DragOverlay} from '@dnd-kit/react';
import type {DragStartEvent, DragEndEvent} from '@dnd-kit/react';
import {move} from '@dnd-kit/helpers';
import {SortableItem} from './SortableItem';

export function SortableList() {
  const [items, setItems] = useState(['Item 1', 'Item 2', 'Item 3', 'Item 4', 'Item 5']);
  const [activeId, setActiveId] = useState<string | null>(null);

  const handleDragStart = (event: DragStartEvent) => {
    setActiveId(event.operation.source.id as string);
  };

  const handleDragEnd = (event: DragEndEvent) => {
    setItems((currentItems) => move(currentItems, event));
    setActiveId(null);
  };

  return (
    <DragDropProvider 
      onDragStart={handleDragStart}
      onDragEnd={handleDragEnd}
    >
      <div style={{maxWidth: '400px', margin: '0 auto', padding: '20px'}}>
        <h2>Sortable List with Overlay</h2>
        <div>
          {items.map((item, index) => (
            <SortableItem key={item} id={item} index={index} />
          ))}
        </div>
      </div>
      
      <DragOverlay>
        {activeId ? (
          <div style={{
            padding: '16px 20px',
            backgroundColor: '#fff',
            border: '1px solid #ddd',
            borderRadius: '8px',
            boxShadow: '0 10px 30px rgba(0,0,0,0.3)',
          }}>
            {activeId}
          </div>
        ) : null}
      </DragOverlay>
    </DragDropProvider>
  );
}
The DragOverlay renders a custom element that follows the cursor during drag operations.

Advanced features

Now that you have the basics down, explore these advanced features:

Drag handles

Add a specific handle that must be grabbed to initiate dragging:
const handleRef = useRef<HTMLButtonElement>(null);

const {isDragging} = useDraggable({
  id: 'item',
  element,
  handle: handleRef,
});

Modifiers

Restrict movement to an axis or snap to grid:
import {RestrictToVerticalAxis} from '@dnd-kit/abstract/modifiers';

<DragDropProvider modifiers={[RestrictToVerticalAxis]}>
  {/* ... */}
</DragDropProvider>

Multiple containers

Create complex layouts with multiple droppable containers and transfer items between them.

Custom sensors

Configure activation constraints and custom input methods:
import {PointerSensor} from '@dnd-kit/react';

const sensors = [
  PointerSensor.configure({
    activationConstraint: {
      distance: 10, // 10px movement required
    },
  }),
];

Event lifecycle

Understand the drag and drop event lifecycle:
1

beforedragstart

Fired before a drag operation begins. You can prevent the drag by calling event.preventDefault().
2

dragstart

Fired when a drag operation starts. Perfect for tracking analytics or updating UI state.
3

dragmove

Fired continuously as the draggable moves. Use sparingly for performance.
4

dragover

Fired when a draggable moves over a droppable. Ideal for visual feedback.
5

dragend

Fired when a drag operation completes, whether successful or cancelled. Update your data here.
<DragDropProvider
  onBeforeDragStart={(event) => {
    console.log('About to start dragging', event.operation.source.id);
  }}
  onDragStart={(event) => {
    console.log('Started dragging', event.operation.source.id);
  }}
  onDragOver={(event) => {
    console.log('Dragging over', event.operation.target?.id);
  }}
  onDragEnd={(event) => {
    console.log('Finished dragging', event.operation);
  }}
>
  {/* ... */}
</DragDropProvider>

Common patterns

Optimistic updates

Update the UI immediately during drag for a snappier feel:
function SortableList() {
  const [items, setItems] = useState(['A', 'B', 'C']);

  return (
    <DragDropProvider
      onDragOver={(event) => {
        // Update immediately as user drags
        setItems((items) => move(items, event));
      }}
      onDragEnd={(event) => {
        // Final update (in case dragover was missed)
        setItems((items) => move(items, event));
      }}
    >
      {/* ... */}
    </DragDropProvider>
  );
}

Conditional dropping

Control what can be dropped where:
const {isDropTarget} = useDroppable({
  id: 'container-1',
  element,
  accept: ['type-a', 'type-b'], // Only accept certain types
});

Disabled items

Temporarily disable dragging:
const {isDragging} = useDraggable({
  id: 'item-1',
  element,
  disabled: isProcessing, // Disable during async operations
});

Next steps

You’ve built your first drag and drop interfaces! Continue learning:

API reference

Explore the complete API documentation

Examples

Browse more complex examples and patterns

Accessibility

Learn about keyboard navigation and screen reader support

Performance

Optimize your drag and drop for large lists

Build docs developers (and LLMs) love