Skip to main content
The @angular/cdk/drag-drop package provides directives and utilities for creating drag-and-drop interfaces, including sortable lists and free-form dragging.

Installation

npm install @angular/cdk
import {DragDropModule} from '@angular/cdk/drag-drop';

Basic Dragging

Free Drag

Make any element draggable:
<div class="box" cdkDrag>
  I can be dragged anywhere!
</div>
.box {
  width: 200px;
  height: 200px;
  border: solid 1px #ccc;
  cursor: move;
}

.cdk-drag-preview {
  box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2);
  opacity: 0.8;
}

.cdk-drag-animating {
  transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}

Sortable List

Create a sortable list:
import {Component} from '@angular/core';
import {CdkDragDrop, moveItemInArray} from '@angular/cdk/drag-drop';

@Component({
  selector: 'app-sortable-list',
  template: `
    <div cdkDropList (cdkDropListDropped)="drop($event)">
      <div *ngFor="let item of items" cdkDrag>
        {{ item }}
      </div>
    </div>
  `,
})
export class SortableList {
  items = ['Item 1', 'Item 2', 'Item 3', 'Item 4'];

  drop(event: CdkDragDrop<string[]>) {
    moveItemInArray(this.items, event.previousIndex, event.currentIndex);
  }
}

Multiple Lists

Move items between lists:
import {Component} from '@angular/core';
import {
  CdkDragDrop,
  moveItemInArray,
  transferArrayItem,
} from '@angular/cdk/drag-drop';

@Component({
  selector: 'app-multi-list',
  template: `
    <div class="container">
      <div class="list">
        <h2>To Do</h2>
        <div 
          cdkDropList
          #todoList="cdkDropList"
          [cdkDropListData]="todo"
          [cdkDropListConnectedTo]="[doneList]"
          (cdkDropListDropped)="drop($event)">
          <div *ngFor="let item of todo" cdkDrag>{{ item }}</div>
        </div>
      </div>

      <div class="list">
        <h2>Done</h2>
        <div 
          cdkDropList
          #doneList="cdkDropList"
          [cdkDropListData]="done"
          [cdkDropListConnectedTo]="[todoList]"
          (cdkDropListDropped)="drop($event)">
          <div *ngFor="let item of done" cdkDrag>{{ item }}</div>
        </div>
      </div>
    </div>
  `,
  styles: [`
    .container { display: flex; gap: 20px; }
    .list { flex: 1; border: 1px solid #ccc; padding: 10px; }
  `]
})
export class MultiList {
  todo = ['Task 1', 'Task 2', 'Task 3'];
  done = ['Completed task'];

  drop(event: CdkDragDrop<string[]>) {
    if (event.previousContainer === event.container) {
      moveItemInArray(
        event.container.data,
        event.previousIndex,
        event.currentIndex
      );
    } else {
      transferArrayItem(
        event.previousContainer.data,
        event.container.data,
        event.previousIndex,
        event.currentIndex
      );
    }
  }
}

Advanced Features

Custom Drag Handle

<div cdkDrag>
  <div cdkDragHandle>
    <svg width="24" height="24" viewBox="0 0 24 24">
      <path d="M3 15h18v-2H3v2zm0 4h18v-2H3v2zm0-8h18V9H3v2zm0-6v2h18V5H3z"/>
    </svg>
  </div>
  
  <div>Item content (only handle is draggable)</div>
</div>

Custom Preview

<div cdkDrag>
  Item content
  
  <div *cdkDragPreview class="custom-preview">
    Custom drag preview
  </div>
</div>

Custom Placeholder

<div cdkDrag>
  Item content
  
  <div *cdkDragPlaceholder class="custom-placeholder">
    Drop here
  </div>
</div>

Constrain Dragging

<!-- Lock to horizontal axis -->
<div cdkDrag cdkDragLockAxis="x">
  Horizontal only
</div>

<!-- Lock to vertical axis -->
<div cdkDrag cdkDragLockAxis="y">
  Vertical only
</div>

<!-- Constrain to boundary -->
<div class="boundary">
  <div cdkDrag [cdkDragBoundary]="'.boundary'">
    Can't leave boundary
  </div>
</div>

Drag Delay

<!-- Delay before drag starts (ms) -->
<div cdkDrag [cdkDragStartDelay]="200">
  Drag after 200ms
</div>

<!-- Different delays for touch -->
<div cdkDrag [cdkDragStartDelay]="{touch: 500, mouse: 0}">
  Touch requires 500ms hold
</div>

Disabled Dragging

<div cdkDrag [cdkDragDisabled]="isDisabled">
  Conditionally draggable
</div>

<div cdkDropList [cdkDropListDisabled]="isLocked">
  <div *ngFor="let item of items" cdkDrag>{{ item }}</div>
</div>

Events

@Component({
  template: `
    <div 
      cdkDrag
      (cdkDragStarted)="onDragStarted($event)"
      (cdkDragMoved)="onDragMoved($event)"
      (cdkDragEnded)="onDragEnded($event)"
      (cdkDragReleased)="onDragReleased($event)">
      Draggable with events
    </div>
  `,
})
export class DragEvents {
  onDragStarted(event: CdkDragStart) {
    console.log('Drag started', event);
  }

  onDragMoved(event: CdkDragMove) {
    console.log('Drag moved', event.pointerPosition);
  }

  onDragEnded(event: CdkDragEnd) {
    console.log('Drag ended at', event.dropPoint);
  }

  onDragReleased(event: CdkDragRelease) {
    console.log('Drag released');
  }
}

API Reference

cdkDrag Directive

Selector: [cdkDrag]
InputTypeDescription
cdkDragDataTArbitrary data attached to drag instance
cdkDragLockAxis'x' | 'y'Lock dragging to axis
cdkDragBoundarystring | ElementRef | HTMLElementElement that limits dragging
cdkDragDisabledbooleanWhether dragging is disabled
cdkDragStartDelaynumber | {touch, mouse}Delay before drag starts
cdkDragConstrainPosition(point, ref, dimensions) => pointConstrain position function
cdkDragFreeDragPosition{x, y}Position for free drag items
cdkDragPreviewClassstring | string[]Classes for preview element
OutputTypeDescription
cdkDragStartedCdkDragStartEmitted when drag starts
cdkDragMovedCdkDragMoveEmitted as drag moves
cdkDragEndedCdkDragEndEmitted when drag ends
cdkDragReleasedCdkDragReleaseEmitted when user releases drag
cdkDragEnteredCdkDragEnterEmitted when enters drop list
cdkDragExitedCdkDragExitEmitted when exits drop list

cdkDropList Directive

Selector: [cdkDropList]
InputTypeDescription
cdkDropListDataT[]Data array for the list
cdkDropListConnectedToCdkDropList[]Other lists to connect to
cdkDropListOrientation'horizontal' | 'vertical'Layout orientation
cdkDropListDisabledbooleanWhether dropping is disabled
cdkDropListSortingDisabledbooleanDisable internal sorting
cdkDropListEnterPredicate(drag, drop) => booleanPredicate for accepting items
OutputTypeDescription
cdkDropListDroppedCdkDragDropEmitted when item is dropped
cdkDropListEnteredCdkDragEnterEmitted when drag enters
cdkDropListExitedCdkDragExitEmitted when drag exits
cdkDropListSortedCdkDragSortEventEmitted when list is sorted

Utility Functions

moveItemInArray

Move an item within an array:
moveItemInArray(array, fromIndex, toIndex);

transferArrayItem

Transfer item between arrays:
transferArrayItem(fromArray, toArray, fromIndex, toIndex);

copyArrayItem

Copy item between arrays:
copyArrayItem(fromArray, toArray, fromIndex, toIndex);

Accessibility

<div 
  cdkDrag
  role="button"
  [attr.aria-grabbed]="isDragging"
  tabindex="0">
  Accessible draggable item
</div>

Best Practices

  1. Provide visual feedback - Use preview and placeholder templates
  2. Handle both touch and mouse - Test on mobile devices
  3. Add ARIA attributes - Make drag-drop accessible
  4. Optimize performance - Limit DOM updates during drag
  5. Preserve data - Use moveItemInArray to maintain array references

See Also

Build docs developers (and LLMs) love