Skip to main content
Draggables and droppables are the fundamental entities in dnd-kit. They represent elements that can be dragged and elements that can receive dragged items.

Entity base class

Both Draggable and Droppable extend a common Entity base class:
class Entity<T extends Data = Data> {
  id: UniqueIdentifier;
  data: T;
  disabled: boolean;
  manager?: DragDropManager;
  
  register(): CleanupFunction | void;
  unregister(): void;
  destroy(): void;
}

Unique identifier

Every entity has a unique identifier that can be a string or number:
type UniqueIdentifier = string | number;
The id is used to:
  • Register/unregister entities in the manager’s registry
  • Identify entities during drag operations
  • Look up entities by ID
  • Track entity relationships
Identifiers must be unique within their entity type. Having two draggables with the same ID will cause unexpected behavior.

Data property

The data property stores custom data associated with the entity:
type Data = Record<string, any>;

const draggable = new Draggable({
  id: 'item-1',
  element: myElement,
  data: {
    title: 'My Item',
    category: 'featured',
    metadata: { /* ... */ },
  },
});
You can access this data during drag operations:
manager.monitor.addEventListener('dragend', (event) => {
  const {source, target} = event.operation;
  
  console.log('Dragged:', source?.data);
  console.log('Dropped on:', target?.data);
});

Disabled state

You can disable entities to prevent interaction:
draggable.disabled = true;  // Can't be dragged
droppable.disabled = true;  // Can't receive drops
Disabled entities:
  • Won’t respond to sensor input (draggables)
  • Won’t participate in collision detection (droppables)
  • Remain in the registry but are skipped during operations

Draggable

A Draggable represents an element that can be dragged.

Creating a draggable

import {Draggable} from '@dnd-kit/dom';

const draggable = new Draggable(
  {
    id: 'item-1',
    element: document.getElementById('item-1'),
  },
  manager
);

Type signature

interface DraggableInput<T extends Data = Data> {
  id: UniqueIdentifier;
  element?: Element;
  handle?: Element;
  data?: T;
  disabled?: boolean;
  type?: Type;
  sensors?: Sensors;
  modifiers?: Modifiers;
  plugins?: Plugins;
  alignment?: Alignment;
  register?: boolean;  // Default: true
}

class Draggable<T extends Data = Data> extends Entity<T> {
  element?: Element;           // The draggable element
  handle?: Element;            // Optional drag handle
  type?: Type;                 // Category/type identifier
  sensors?: Sensors;           // Per-entity sensors
  modifiers?: Modifiers;       // Per-entity modifiers
  plugins?: Plugins;           // Per-entity plugins
  alignment?: Alignment;       // Positioning alignment
  
  status: DraggableStatus;     // Current status
  isDragging: boolean;         // Currently being dragged
  isDropping: boolean;         // Currently being dropped
  isDragSource: boolean;       // Is the drag source
}

Element and handle

The element is the DOM element that represents the draggable:
const draggable = new Draggable({
  id: 'item-1',
  element: document.getElementById('item-1'),
});
Optionally specify a handle to restrict where dragging can start:
const draggable = new Draggable({
  id: 'item-1',
  element: document.getElementById('item-1'),
  handle: document.querySelector('#item-1 .drag-handle'),
});
When a handle is specified:
  • Dragging only starts when interacting with the handle
  • The rest of the element remains interactive
  • Useful for complex UI elements with buttons, links, etc.

Status tracking

Draggables track their current status:
type DraggableStatus = 'idle' | 'dragging' | 'dropping';

// Check status
if (draggable.status === 'dragging') {
  // Currently being dragged
}

// Convenience getters
if (draggable.isDragging) {
  // Same as status === 'dragging' && isDragSource
}

if (draggable.isDragSource) {
  // This draggable is the source of the current drag
}
Status transitions:
idle → dragging → dropping → idle

       idle (if canceled)

Type categorization

The type property categorizes draggables:
const draggable = new Draggable({
  id: 'item-1',
  element: myElement,
  type: 'card',
});
Droppables can accept only specific types:
const droppable = new Droppable({
  id: 'container-1',
  element: containerElement,
  accept: 'card',  // Only accepts 'card' type draggables
});

Per-entity configuration

You can override manager settings for individual draggables:
import {PointerSensor} from '@dnd-kit/dom';
import {RestrictToVerticalAxis} from '@dnd-kit/abstract';

const draggable = new Draggable({
  id: 'item-1',
  element: myElement,
  
  // Use specific sensors for this draggable
  sensors: [PointerSensor],
  
  // Apply modifiers only to this draggable
  modifiers: [RestrictToVerticalAxis],
  
  // Configure plugins for this draggable
  plugins: [
    MyPlugin.configure({ /* options */ }),
  ],
});

Droppable

A Droppable represents an element that can receive dragged items.

Creating a droppable

import {Droppable} from '@dnd-kit/dom';
import {pointerIntersection} from '@dnd-kit/dom';

const droppable = new Droppable(
  {
    id: 'container-1',
    element: document.getElementById('container-1'),
    collisionDetector: pointerIntersection,
  },
  manager
);

Type signature

interface DroppableInput<T extends Data = Data> {
  id: UniqueIdentifier;
  element?: Element;
  data?: T;
  disabled?: boolean;
  type?: Type;
  accept?: Type | Type[] | ((source: Draggable) => boolean);
  collisionDetector: CollisionDetector;
  collisionPriority?: CollisionPriority | number;
  register?: boolean;  // Default: true
}

class Droppable<T extends Data = Data> extends Entity<T> {
  element?: Element;                  // The droppable element
  type?: Type;                        // Category/type identifier
  accept?: Type | Type[] | Function;  // Acceptance rules
  collisionDetector: CollisionDetector;
  collisionPriority?: CollisionPriority | number;
  shape?: Shape;                      // Current bounding rectangle
  
  isDropTarget: boolean;              // Is the current drop target
  accepts(draggable: Draggable): boolean;
}

Collision detection

Every droppable requires a collision detector:
import {
  pointerIntersection,
  shapeIntersection,
  centerOfMass,
  closestCorners,
} from '@dnd-kit/dom';

const droppable = new Droppable({
  id: 'container-1',
  element: containerElement,
  collisionDetector: pointerIntersection,  // Required
});
See Collision detection for available detectors.

Acceptance rules

Control which draggables can be dropped: Accept a specific type:
const droppable = new Droppable({
  id: 'cards-container',
  element: containerElement,
  accept: 'card',
  collisionDetector: pointerIntersection,
});
Accept multiple types:
const droppable = new Droppable({
  id: 'mixed-container',
  element: containerElement,
  accept: ['card', 'token', 'piece'],
  collisionDetector: pointerIntersection,
});
Custom acceptance function:
const droppable = new Droppable({
  id: 'special-container',
  element: containerElement,
  accept: (draggable) => {
    // Custom logic
    return draggable.data?.category === 'premium';
  },
  collisionDetector: pointerIntersection,
});
Accept all (default):
const droppable = new Droppable({
  id: 'universal-container',
  element: containerElement,
  // accept: undefined (default - accepts all)
  collisionDetector: pointerIntersection,
});

Collision priority

When multiple droppables collide, priority determines the winner:
enum CollisionPriority {
  Lowest,   // 0
  Low,      // 1
  Normal,   // 2 (default)
  High,     // 3
  Highest,  // 4
}

const droppable = new Droppable({
  id: 'priority-container',
  element: containerElement,
  collisionDetector: pointerIntersection,
  collisionPriority: CollisionPriority.High,
});
You can also use custom numeric priorities:
const droppable = new Droppable({
  id: 'custom-priority',
  element: containerElement,
  collisionDetector: pointerIntersection,
  collisionPriority: 100,  // Custom priority
});

Shape tracking

Droppables track their bounding rectangle:
const {shape} = droppable;

if (shape) {
  console.log('Position:', shape.x, shape.y);
  console.log('Size:', shape.width, shape.height);
}
The shape is automatically updated during drag operations.

Drop target status

Check if a droppable is the current drop target:
if (droppable.isDropTarget) {
  // This droppable is the target of the current drag
}

Registration

Entities automatically register with the manager when created:
const draggable = new Draggable(
  {id: 'item-1', element: myElement},
  manager  // Automatically registers
);

Manual registration

You can disable automatic registration:
const draggable = new Draggable(
  {
    id: 'item-1',
    element: myElement,
    register: false,  // Don't auto-register
  },
  manager
);

// Register manually later
const cleanup = draggable.register();

// Unregister when done
cleanup();
// or
draggable.unregister();

Registration lifecycle

Entities can register effects that run when registered:
const draggable = new Draggable(
  {
    id: 'item-1',
    element: myElement,
    effects: () => [
      // Effect runs when registered
      () => {
        console.log('Registered');
        
        // Cleanup runs when unregistered
        return () => console.log('Unregistered');
      },
    ],
  },
  manager
);

Reactive properties

Many entity properties are reactive:
import {effect} from '@dnd-kit/state';

// React to property changes
effect(() => {
  if (draggable.isDragging) {
    console.log('Started dragging');
  }
});

effect(() => {
  if (droppable.isDropTarget) {
    console.log('Is drop target');
  }
});
Reactive properties:
  • disabled
  • data
  • type
  • status (draggables)
  • accept (droppables)
  • shape (droppables)
  • modifiers (draggables)

Type safety

You can type the data property:
interface CardData {
  title: string;
  category: string;
  priority: number;
}

const draggable = new Draggable<CardData>(
  {
    id: 'card-1',
    element: myElement,
    data: {
      title: 'My Card',
      category: 'tasks',
      priority: 1,
    },
  },
  manager
);

// Type-safe access
const title: string = draggable.data.title;

Framework integration

Framework adapters provide idiomatic APIs:
// React example
import {useDraggable, useDroppable} from '@dnd-kit/react';

function DraggableCard({id, data}) {
  const {ref} = useDraggable({
    id,
    data,
    type: 'card',
  });
  
  return <div ref={ref}>Card</div>;
}

function DroppableContainer({id}) {
  const {ref} = useDroppable({
    id,
    accept: 'card',
  });
  
  return <div ref={ref}>Container</div>;
}

Draggable API

Draggable API reference

Droppable API

Droppable API reference

Collision detection

How collisions work

Data flow

Working with entity data

Build docs developers (and LLMs) love