How to build a Kanban board in Angular
Building a functional and performant Kanban board in Angular is a common requirement for project management and CRM applications.
With over 25 years of experience in software development and as the creator of CoreUI, I have built dozens of drag-and-drop interfaces for enterprise-grade dashboards.
The most efficient and modern solution is to leverage the @angular/cdk/drag-drop module, which handles the complex physics and accessibility of moving items between lists.
By combining this with CoreUI components, you can create a board that is both highly functional and visually polished.
Use the Angular CDK DragDropModule to manage task movement between columns and cdkDropListGroup to link lists together.
1. Install and Import CDK
First, ensure you have the Angular CDK installed in your project. If you are starting fresh, you can check our guide on how to create a new Angular project.
npm install @angular/cdk
The CDK provides the DragDropModule which handles all the drag-and-drop physics and accessibility. CoreUI components are used for the card styling and layout grid.
2. Define the Board Data Structure
A Kanban board typically consists of several columns, each containing an array of tasks. Keeping your data model clean is essential for performance.
// kanban.component.ts
import { Component } from '@angular/core'
import { CommonModule } from '@angular/common'
import {
CdkDragDrop,
DragDropModule,
moveItemInArray,
transferArrayItem
} from '@angular/cdk/drag-drop'
import { CardModule, GridModule, ContainerComponent } from '@coreui/angular'
@Component({
selector: 'app-kanban',
standalone: true,
imports: [CommonModule, DragDropModule, CardModule, GridModule, ContainerComponent],
templateUrl: './kanban.component.html',
styleUrls: ['./kanban.component.scss']
})
export class KanbanComponent {
todo = ['Setup project', 'Install CoreUI', 'Review PRs']
inProgress = ['Developing Kanban', 'Testing API']
done = ['Initial commit', 'Documentation']
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
)
}
}
}
The drop method is the brain of the operation. It detects whether an item was moved within the same list or transferred to a different column.
3. Creating the Kanban Template
We use cdkDropListGroup to automatically link all drop lists within the container. We also utilize CoreUI Cards for a professional look.
<!-- kanban.component.html -->
<div class="kanban-wrapper" cdkDropListGroup>
<div class="kanban-column">
<h3>To Do</h3>
<div
cdkDropList
[cdkDropListData]="todo"
(cdkDropListDropped)="drop($event)"
class="kanban-list"
>
<c-card *ngFor="let item of todo" cdkDrag class="kanban-item">
<c-card-body>{{ item }}</c-card-body>
</c-card>
</div>
</div>
<div class="kanban-column">
<h3>In Progress</h3>
<div
cdkDropList
[cdkDropListData]="inProgress"
(cdkDropListDropped)="drop($event)"
class="kanban-list"
>
<c-card *ngFor="let item of inProgress" cdkDrag class="kanban-item">
<c-card-body>{{ item }}</c-card-body>
</c-card>
</div>
</div>
<div class="kanban-column">
<h3>Done</h3>
<div
cdkDropList
[cdkDropListData]="done"
(cdkDropListDropped)="drop($event)"
class="kanban-list"
>
<c-card *ngFor="let item of done" cdkDrag class="kanban-item">
<c-card-body>{{ item }}</c-card-body>
</c-card>
</div>
</div>
</div>
The cdkDropList directive identifies a droppable area, while cdkDrag marks the individual cards as draggable elements.
4. Styling the Board Layout
For a proper Kanban feel, we need a horizontal layout that allows columns to scroll if they exceed the screen width.
// kanban.component.scss
.kanban-wrapper {
display: flex;
gap: 1.5rem;
padding: 1rem;
overflow-x: auto;
align-items: flex-start;
}
.kanban-column {
background: #f8f9fa;
border-radius: 8px;
width: 300px;
min-width: 300px;
padding: 1rem;
}
.kanban-list {
min-height: 200px;
display: block;
}
.kanban-item {
margin-bottom: 0.75rem;
cursor: grab;
&:active {
cursor: grabbing;
}
}
.cdk-drag-preview {
box-sizing: border-box;
border-radius: 4px;
box-shadow: 0 5px 15px rgba(0,0,0,0.15);
}
These styles ensure the columns stay side-by-side and provide the user with clear visual feedback during the drag interaction.
5. Enhancing with CoreUI Templates
For large-scale applications, you might want to integrate this Kanban board into a full dashboard environment. Using the Angular Dashboard Template provides you with pre-built navigation, headers, and state management that can wrap your Kanban component seamlessly.
<!-- Integration example -->
<c-container breakpoint="fluid">
<c-row>
<c-col>
<app-kanban></app-kanban>
</c-col>
</c-row>
</c-container>
This ensures your board fits perfectly within the rest of your admin panel’s design system.
6. Handling Empty Columns
One common issue is losing the ability to drop an item into an empty column. We solve this by ensuring the list container has a minimum height and padding.
/* Ensure empty lists can still receive items */
.kanban-list {
min-height: 400px;
border: 2px dashed transparent;
transition: border-color 0.2s ease;
}
.cdk-drop-list-dragging .kanban-list {
border-color: #dee2e6;
}
By adding a dashed border during the drag operation, you guide the user to valid drop targets, improving the overall UX.
7. Performance and Optimization
When dealing with hundreds of tasks, you should implement trackBy in your *ngFor loops to prevent unnecessary DOM re-rendering.
// In your component
trackByFn(index: number, item: any) {
return index // or item.id
}
// In your HTML
// <c-card *ngFor="let item of todo; trackBy: trackByFn" cdkDrag>
This is the same approach we use in CoreUI components like the Smart Table to ensure smooth performance regardless of data size.
Best Practice Note:
Always persist the new order of items to your backend database after the drop event completes.
For a complete enterprise solution, consider using our Free Angular Admin Template which includes all the scaffolding needed for stateful applications.
Avoid over-complicating the drag logic; the CDK’s built-in moveItemInArray and transferArrayItem are highly optimized for these specific use cases.



