This article contains interactive demos, to see them in action visit it at https://angular-material.dev/articles/angular-cdk-drag-drop-mixed
Angular CDK Drag & Drop
The @angular/cdk/drag-drop
module allows you to effortlessly and declaratively build drag-and-drop interfaces. It supports free dragging, list sorting, item transfers between lists, animations, touch devices, custom drag handles, previews, placeholders, horizontal lists, and axis locking.
Reordering lists
Wrapping a set of cdkDrag
elements with cdkDropList
groups them into a reorderable collection. As elements move, they will automatically rearrange. However, this won't update your data model; you can listen to the cdkDropListDropped
event to update the data model after the user finishes dragging.
<div cdkDropList class="example-list" (cdkDropListDropped)="drop($event)"> @for (movie of movies; track movie) { <div class="example-box" cdkDrag>{{movie}}</div> } </div>
import { Component } from "@angular/core"; import { CdkDrag, CdkDragDrop, CdkDropList, moveItemInArray, } from "@angular/cdk/drag-drop"; @Component({ selector: "app-basic-drag-drop", templateUrl: "basic.component.html", styleUrl: "basic.component.css", standalone: true, imports: [CdkDropList, CdkDrag], }) export class BasicDragDropExampleComponent { movies = [ "Episode I - The Phantom Menace", "Episode II - Attack of the Clones", "Episode III - Revenge of the Sith", "Episode IV - A New Hope", "Episode V - The Empire Strikes Back", "Episode VI - Return of the Jedi", "Episode VII - The Force Awakens", "Episode VIII - The Last Jedi", "Episode IX - The Rise of Skywalker", ]; drop(event: CdkDragDrop<string[]>) { moveItemInArray( this.movies, event.previousIndex, event.currentIndex ); } }
.example-list { width: 500px; max-width: 100%; border: solid 1px #ccc; min-height: 60px; display: block; background: white; border-radius: 4px; overflow: hidden; } .example-box { padding: 20px 10px; border-bottom: solid 1px #ccc; color: rgba(0, 0, 0, 0.87); display: flex; flex-direction: row; align-items: center; justify-content: space-between; box-sizing: border-box; cursor: move; background: white; font-size: 14px; } .cdk-drag-preview { border: none; box-sizing: border-box; border-radius: 4px; box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2), 0 8px 10px 1px rgba(0, 0, 0, 0.14), 0 3px 14px 2px rgba(0, 0, 0, 0.12); } .cdk-drag-placeholder { opacity: 0; } .cdk-drag-animating { transition: transform 250ms cubic-bezier(0, 0, 0.2, 1); } .example-box:last-child { border: none; } .example-list.cdk-drop-list-dragging .example-box:not(.cdk-drag-placeholder) { transition: transform 250ms cubic-bezier(0, 0, 0.2, 1); }
Orientations
Angular CDK supports 2 orientations for reordering lists: vertical (default) and horizontal.
The cdkDropList
directive assumes that lists are vertical by default. This can be changed by setting the cdkDropListOrientation
property to horizontal
.
<div cdkDropList cdkDropListOrientation="horizontal" class="example-list" (cdkDropListDropped)="drop($event)"> @for (timePeriod of timePeriods; track timePeriod) { <div class="example-box" cdkDrag>{{timePeriod}}</div> } </div>
import {Component} from '@angular/core'; import {CdkDragDrop, CdkDrag, CdkDropList, moveItemInArray} from '@angular/cdk/drag-drop'; @Component({ selector: 'app-horizontal-drag-drop', templateUrl: 'horizontal.component.html', styleUrl: 'horizontal.component.css', standalone: true, imports: [CdkDropList, CdkDrag], }) export class HorizontalDragDropExampleComponent { timePeriods = [ 'Bronze age', 'Iron age', 'Middle ages', 'Early modern period', 'Long nineteenth century', ]; drop(event: CdkDragDrop<string[]>) { moveItemInArray(this.timePeriods, event.previousIndex, event.currentIndex); } }
.example-list { width: 54vw; max-width: 100%; border: solid 1px #ccc; min-height: 60px; display: flex; flex-direction: row; background: white; border-radius: 4px; overflow: hidden; } .example-box { padding: 20px 10px; border-right: solid 1px #ccc; color: rgba(0, 0, 0, 0.87); display: flex; flex-direction: row; align-items: center; justify-content: space-between; box-sizing: border-box; cursor: move; background: white; font-size: 14px; flex-grow: 1; flex-basis: 0; } .cdk-drag-preview { box-sizing: border-box; border-radius: 4px; box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2), 0 8px 10px 1px rgba(0, 0, 0, 0.14), 0 3px 14px 2px rgba(0, 0, 0, 0.12); } .cdk-drag-placeholder { opacity: 0; } .cdk-drag-animating { transition: transform 250ms cubic-bezier(0, 0, 0.2, 1); } .example-box:last-child { border: none; } .example-list.cdk-drop-list-dragging .example-box:not(.cdk-drag-placeholder) { transition: transform 250ms cubic-bezier(0, 0, 0.2, 1); }
Mixed Orientation
By default, cdkDropList
sorts items by moving them with a CSS transform. This enables animated sorting for a better user experience, but it only works in one direction: either vertically or horizontally.
From Angular Material v18.1.0
, for sortable lists that need to wrap, you can set cdkDropListOrientation="mixed"
. This uses a different sorting strategy by moving elements in the DOM, allowing items to wrap to the next line. However, it cannot animate the sorting action.
<div cdkDropList cdkDropListOrientation="mixed" class="example-list" (cdkDropListDropped)="drop($event)"> @for (item of items; track item) { <div class="example-box" cdkDrag>{{item}}</div> } </div>
import {Component} from '@angular/core'; import {CdkDragDrop, CdkDrag, CdkDropList, moveItemInArray} from '@angular/cdk/drag-drop'; /** * @title Drag&Drop horizontal wrapping list */ @Component({ selector: 'app-mixed-drag-drop', templateUrl: 'mixed.component.html', styleUrl: 'mixed.component.css', standalone: true, imports: [CdkDropList, CdkDrag], }) export class MixedDragDropExampleComponent { items = ['Zero', 'One', 'Two', 'Three', 'Four', 'Five', 'Six', 'Seven', 'Eight', 'Nine']; drop(event: CdkDragDrop<string[]>) { moveItemInArray(this.items, event.previousIndex, event.currentIndex); } }
.example-list { display: flex; flex-wrap: wrap; width: 505px; max-width: 100%; gap: 15px; padding: 15px; border: solid 1px #ccc; min-height: 60px; border-radius: 4px; overflow: hidden; } .example-box { padding: 20px 10px; border: solid 1px #ccc; border-radius: 4px; color: rgba(0, 0, 0, 0.87); display: inline-block; box-sizing: border-box; cursor: move; background: white; text-align: center; font-size: 14px; min-width: 115px; } .cdk-drag-preview { box-sizing: border-box; border-radius: 4px; box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2), 0 8px 10px 1px rgba(0, 0, 0, 0.14), 0 3px 14px 2px rgba(0, 0, 0, 0.12); } .cdk-drag-placeholder { opacity: 0; } .cdk-drag-animating { transition: transform 250ms cubic-bezier(0, 0, 0.2, 1); }
Top comments (1)
Hi Dharmen Shah,
Top, very nice and helpful !
Thanks for sharing.