Skip to main content
The useDroppable hook creates a drop target that can receive draggable elements. It registers the element with the drag and drop manager and provides reactive state about whether it’s being targeted.

Usage

import {useDroppable} from '@dnd-kit/react';

function DroppableArea({id}) {
  const {ref, isDropTarget} = useDroppable({id});

  return (
    <div
      ref={ref}
      style={{
        padding: '20px',
        border: isDropTarget ? '2px solid green' : '2px dashed gray',
      }}
    >
      Drop here
    </div>
  );
}

Parameters

id
string | number
required
Unique identifier for this droppable element. Used to track the element during drag operations.
const {ref} = useDroppable({id: 'droppable-1'});
element
Element | RefObject<Element>
The DOM element to make droppable. If not provided, use the ref from the return value.
const elementRef = useRef(null);
useDroppable({id: 'area', element: elementRef});
data
T
Custom data to attach to this droppable. Accessible in event handlers when elements are dropped.
const {ref} = useDroppable({
  id: 'category-a',
  data: {category: 'A', maxItems: 10},
});
disabled
boolean
default:"false"
Whether this droppable is disabled. Disabled droppables cannot receive drops.
const {ref} = useDroppable({
  id: 'area',
  disabled: true,
});
accept
string | string[]
Type or types of draggables this droppable accepts. If not specified, accepts all types.
// Accept only specific types
const {ref} = useDroppable({
  id: 'area',
  accept: ['file', 'folder'],
});
type
string
Type identifier for this droppable. Can be used with collision detection or custom logic.
const {ref} = useDroppable({
  id: 'container',
  type: 'container',
});
collisionDetector
CollisionDetector
Custom collision detection algorithm for this droppable. Defaults to defaultCollisionDetection.
import {closestCenter} from '@dnd-kit/collision';

const {ref} = useDroppable({
  id: 'area',
  collisionDetector: closestCenter,
});

Return Value

ref
(element: Element | null) => void
Callback ref to attach to the droppable element.
const {ref} = useDroppable({id: 'area'});
return <div ref={ref}>Drop zone</div>;
droppable
Droppable<T>
The underlying Droppable instance. Access this for advanced use cases.
isDropTarget
boolean
Whether this element is currently the target of an active drag operation.
const {ref, isDropTarget} = useDroppable({id: 'area'});
return (
  <div
    ref={ref}
    style={{
      background: isDropTarget ? 'lightgreen' : 'white',
    }}
  >
    {isDropTarget ? 'Release to drop' : 'Drop here'}
  </div>
);

Examples

Basic Drop Zone

function BasicDropZone() {
  const {ref, isDropTarget} = useDroppable({id: 'zone'});

  return (
    <div
      ref={ref}
      style={{
        minHeight: '200px',
        padding: '20px',
        border: isDropTarget ? '3px solid green' : '3px dashed gray',
        background: isDropTarget ? 'rgba(0, 255, 0, 0.1)' : 'white',
        transition: 'all 0.2s',
      }}
    >
      {isDropTarget ? 'Release to drop!' : 'Drag items here'}
    </div>
  );
}

Droppable with Type Restrictions

function CategoryDropZone({category}) {
  const {ref, isDropTarget} = useDroppable({
    id: `category-${category}`,
    accept: ['item'], // Only accept draggables with type 'item'
    data: {category},
  });

  return (
    <div ref={ref} className={isDropTarget ? 'highlighted' : ''}>
      <h3>Category {category}</h3>
      <p>Drop items here</p>
    </div>
  );
}

Droppable with Custom Data

interface DropZoneData {
  category: string;
  maxItems: number;
}

function SmartDropZone({category, maxItems, currentCount}) {
  const isFull = currentCount >= maxItems;
  
  const {ref, isDropTarget} = useDroppable<DropZoneData>({
    id: category,
    disabled: isFull,
    data: {category, maxItems},
  });

  return (
    <div
      ref={ref}
      style={{
        opacity: isFull ? 0.5 : 1,
        border: isDropTarget ? '2px solid blue' : '2px solid gray',
      }}
    >
      <h3>{category}</h3>
      <p>
        {currentCount} / {maxItems} items
      </p>
      {isFull && <span>Full - no more items allowed</span>}
    </div>
  );
}

Nested Droppables

function NestedDroppables() {
  const {ref: outerRef, isDropTarget: isOuterTarget} = useDroppable({
    id: 'outer',
    type: 'container',
  });

  const {ref: innerRef, isDropTarget: isInnerTarget} = useDroppable({
    id: 'inner',
    type: 'list',
  });

  return (
    <div
      ref={outerRef}
      style={{
        padding: '40px',
        border: isOuterTarget ? '3px solid blue' : '3px solid gray',
      }}
    >
      <h2>Outer Container</h2>
      <div
        ref={innerRef}
        style={{
          padding: '20px',
          border: isInnerTarget ? '2px solid green' : '2px dashed gray',
        }}
      >
        <h3>Inner List</h3>
        <p>Drop items here</p>
      </div>
    </div>
  );
}

Multiple Drop Zones with Accept Types

function FileManager() {
  const {ref: folderRef, isDropTarget: isFolderTarget} = useDroppable({
    id: 'folders',
    accept: 'folder',
  });

  const {ref: fileRef, isDropTarget: isFileTarget} = useDroppable({
    id: 'files',
    accept: 'file',
  });

  const {ref: trashRef, isDropTarget: isTrashTarget} = useDroppable({
    id: 'trash',
    accept: ['file', 'folder'], // Accepts both types
  });

  return (
    <div>
      <div ref={folderRef} className={isFolderTarget ? 'highlight' : ''}>
        Folders only
      </div>
      <div ref={fileRef} className={isFileTarget ? 'highlight' : ''}>
        Files only
      </div>
      <div ref={trashRef} className={isTrashTarget ? 'highlight' : ''}>
        🗑️ Trash (accepts anything)
      </div>
    </div>
  );
}

Droppable List

function DroppableList({items, onDrop}) {
  const {ref, isDropTarget} = useDroppable({
    id: 'list',
    data: {type: 'list'},
  });

  return (
    <div
      ref={ref}
      style={{
        minHeight: '300px',
        padding: '20px',
        background: isDropTarget ? '#f0f8ff' : '#fafafa',
        border: isDropTarget ? '2px solid #0066cc' : '1px solid #ddd',
        borderRadius: '8px',
      }}
    >
      {items.length === 0 ? (
        <p style={{textAlign: 'center', color: '#999'}}>
          Drop items here to add them to the list
        </p>
      ) : (
        items.map((item) => <div key={item.id}>{item.label}</div>)
      )}
    </div>
  );
}

Type Safety

Use TypeScript generics to type the custom data:
interface ContainerData {
  category: string;
  capacity: number;
  currentCount: number;
}

function TypedDroppable() {
  const {ref, isDropTarget, droppable} = useDroppable<ContainerData>({
    id: 'container-1',
    data: {
      category: 'electronics',
      capacity: 50,
      currentCount: 12,
    },
  });

  // droppable.data is typed as ContainerData
  return (
    <div ref={ref}>
      {droppable.data.category}: {droppable.data.currentCount} /{' '}
      {droppable.data.capacity}
    </div>
  );
}

Build docs developers (and LLMs) love