Angular CDK Drag & Drop: Multi Direction Movement
In this quick guide, we will learn how to handle movement in both directions (vertical & horizontal) using Angular CDK Drag & Drop
This article contains interactive demos, to see them in action visit it at angular-material.dev/articles/angular-cdk-d..
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);
}