Summarize with AI:
See how the Blazor TaskBoard can equip your app with a handy Kanban-style visualized workflow.
In the modern fast-paced era, teams need more than a simple to-do list. They need structured, visual workflows that reflect real project states, support multiple roles and integrate smoothly into existing processes. The Progress Telerik UI for Blazor TaskBoard, also known as a Kanban Board, is purpose-built for exactly that.
In this post, we will explore the key TaskBoard features and walk through a practical two-role enterprise scenario: an administrator who configures the board and a user who works with the tasks.
The TelerikTaskBoard component displays task and progress information as Cards organized in Columns. The component supports:
The TaskBoard recognizes a set of default model property names out of the box, which means you can bind your data without any extra configuration as long as your classes follow the naming convention. For simplicity, the examples in this blog post will follow the default conventions.
The Status property is the link between Cards and Columns: a Card is rendered inside the Column with a matching Status value. If your domain model uses different property names, you can map them via parameters such as CardStatusField, ColumnStatusField, etc.
public class TaskBoardCard
{
public int Id { get; set; }
public int Index { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public string Status { get; set; }
public string Priority { get; set; }
}
public class TaskBoardColumn
{
public int Index { get; set; }
public string Status { get; set; }
public string Title { get; set; }
public string Width { get; set; }
public int? WipLimit { get; set; }
public bool Enabled { get; set; }
public TaskBoardColumnButtons? Buttons { get; set; }
}
Drag-and-drop is the signature UX feature of any Kanban board, and the TaskBoard delivers it with minimal setup.
Card-dragging is enabled by default (CardDraggable="true"). When a user drops a Card into a different column or a different position within the same column, the OnCardMove event fires. Update the Card’s Index and Status in the handler:
<TelerikTaskBoard OnCardMove="@OnTaskBoardCardMove" />
@code {
private void OnTaskBoardCardMove(TaskBoardCardMoveEventArgs<TaskBoardCard> args)
{
args.Item.Index = args.NewIndex;
args.Item.Status = args.NewStatus;
}
}
Column reordering is disabled by default (ColumnReorderable="false"). When enabled, the OnColumnReorder event gives you the old and new index so you can update the original data source or cancel the operation:
<TelerikTaskBoard ColumnReorderable="true"
OnColumnReorder="@OnColumnReorder" />
@code {
private void OnColumnReorder(TaskBoardColumnReorderEventArgs<TaskBoardColumn> args)
{
// Use args.NewIndex to update the original Column data source
// or
// args.IsCancelled = true; // Revert the reordering
}
}
The TaskBoard supports built-in create, edit and delete operations for both Cards and Columns.
Each operation requires a corresponding event handler that updates your data collection:
<TelerikTaskBoard CardData="@TaskBoardCards"
OnCardCreate="@OnCreate"
OnCardUpdate="@OnUpdate"
OnCardDelete="@OnDelete" />
@code {
private List<TaskBoardCard> TaskBoardCards { get; set; } = new List<TaskBoardCard>();
private void OnCreate(TaskBoardCardCreateEventArgs<TaskBoardCard> args)
{
args.Item.Id = 12345;
TaskBoardCards.Add(args.Item);
}
private void OnUpdate(TaskBoardCardUpdateEventArgs<TaskBoardCard> args)
{
args.OriginalItem.Title = args.Item.Title;
args.OriginalItem.Description = args.Item.Description;
args.OriginalItem.Priority = args.Item.Priority;
}
private void OnDelete(TaskBoardCardDeleteEventArgs<TaskBoardCard> args)
{
TaskBoardCards.Remove(args.Item);
}
}
Column operations follow the same pattern. When a Column is deleted, the app decides what to do with orphaned Cards—keep them, delete them or move them to another column:
<TelerikTaskBoard ColumnData="@TaskBoardColumns"
OnColumnCreate="@OnTaskBoardColumnCreate"
OnColumnDelete="@OnTaskBoardColumnDelete"
OnColumnUpdate="@OnTaskBoardColumnUpdate" />
@code {
private List<TaskBoardColumn> TaskBoardColumns { get; set; } = new List<TaskBoardColumn>();
private void OnTaskBoardColumnCreate(TaskBoardColumnCreateEventArgs<TaskBoardColumn> args)
{
args.Item.Status = "new-unique-status";
TaskBoardColumns.Add(args.Item);
}
private void OnTaskBoardColumnDelete(TaskBoardColumnDeleteEventArgs<TaskBoardColumn> args)
{
TaskBoardColumns.Remove(args.Item);
}
private void OnTaskBoardColumnUpdate(TaskBoardColumnUpdateEventArgs<TaskBoardColumn> args)
{
args.OriginalItem.Title = args.Item.Title;
args.OriginalItem.Status = args.Item.Status;
args.OriginalItem.Width = args.Item.Width;
args.OriginalItem.WipLimit = args.Item.WipLimit;
}
}
The TaskBoardToolBar sits above the Columns and provides both built-in and custom tools. With regard to editing, the ToolBar provides a built-in <TaskBoardToolBarAddColumnTool /> component that renders a Button that fires the OnColumnCreate event.
<TelerikTaskBoard>
<TaskBoardToolBar>
<TaskBoardToolBarAddColumnTool Icon="@SvgIcon.Plus" />
</TaskBoardToolBar>
</TelerikTaskBoard>
A common pattern separates the board configuration from day-to-day usage. The following samples demonstrate this pattern with two TaskBoard instances that share the same column settings.
The administrator starts from an empty TelerikTaskBoard to create and manage the workflow stages (e.g., Backlog, In Progress, Under Review, Done).
A TelerikGrid with inline editing and row drag-and-drop lets the administrator define the available priority levels and assign a color to each. A TelerikColorPalette editor uses color values from the Telerik CSS theme.
@using System.Text.Json
@inject IJSRuntime JS
<h1>TaskBoard Management</h1>
<h2>TaskBoard Columns</h2>
<TelerikTaskBoard CardData="@TaskBoardCards"
ColumnData="@TaskBoardColumns"
ColumnReorderable="true"
Height="300px"
OnColumnCreate="@OnTaskBoardColumnCreate"
OnColumnDelete="@OnTaskBoardColumnDelete"
OnColumnReorder="@OnTaskBoardColumnReorder"
OnColumnUpdate="@OnTaskBoardColumnUpdate"
TColumn="@TaskBoardColumn"
TItem="@TaskBoardCard">
<TaskBoardSettings>
<TaskBoardColumnSettings Buttons="@(TaskBoardColumnButtons.EditColumn | TaskBoardColumnButtons.DeleteColumn)"
Width="300px" />
</TaskBoardSettings>
<TaskBoardToolBar>
<TaskBoardToolBarAddColumnTool Icon="@SvgIcon.Plus" />
</TaskBoardToolBar>
</TelerikTaskBoard>
<h2>Task Priorities</h2>
<TelerikGrid Data="@TaskBoardPriorities"
ConfirmDelete="true"
EditMode="@GridEditMode.Inline"
OnUpdate="@OnGridUpdate"
OnCreate="@OnGridCreate"
OnDelete="@OnGridDelete"
RowDraggable="true"
OnRowDrop="@OnGridRowDrop"
TItem="@TaskBoardCardPriority">
<GridToolBarTemplate>
<GridCommandButton Command="Add">Add Item</GridCommandButton>
</GridToolBarTemplate>
<GridColumns>
<GridColumn Field="@nameof(TaskBoardCardPriority.Text)" />
<GridColumn Field="@nameof(TaskBoardCardPriority.Color)">
<Template>
@{ TaskBoardCardPriority priority = (TaskBoardCardPriority)context; }
<div class="priority-color">
<strong style="background-color: @(priority.Color);"></strong>
<span>@GetColorKeyword(priority.Color)</span>
</div>
</Template>
<EditorTemplate>
@{ TaskBoardCardPriority priority = (TaskBoardCardPriority)context; }
<TelerikColorPalette @bind-Value="@priority.Color"
Colors="@PriorityColors"
Columns="6"
Size="@ThemeConstants.ColorPalette.Size.Large" />
</EditorTemplate>
</GridColumn>
<GridCommandColumn Title="Commands">
<GridCommandButton Command="Edit">Edit</GridCommandButton>
<GridCommandButton Command="Save" ShowInEdit="true">Save</GridCommandButton>
<GridCommandButton Command="Cancel" ShowInEdit="true">Cancel</GridCommandButton>
<GridCommandButton Command="Delete">Delete</GridCommandButton>
</GridCommandColumn>
</GridColumns>
</TelerikGrid>
<TelerikButton OnClick="@OnConfirmTaskBoardClick">Confirm TaskBoard Configuration</TelerikButton>
<style>
.priority-color {
display: flex;
align-items: center;
}
.priority-color > strong {
display: inline-block;
width: 1.2em;
height: 1.2em;
margin-right: .5em;
}
</style>
@code {
private List<TaskBoardCard> TaskBoardCards { get; set; } = new List<TaskBoardCard>();
private List<TaskBoardColumn> TaskBoardColumns { get; set; } = new List<TaskBoardColumn>();
private List<TaskBoardCardPriority> TaskBoardPriorities { get; set; } = new List<TaskBoardCardPriority>()
{
new TaskBoardCardPriority() { Text = "Low", Value = "low", Color = "var(--kendo-color-success)" },
new TaskBoardCardPriority() { Text = "Normal", Value = "normal", Color = "var(--kendo-color-info)" },
new TaskBoardCardPriority() { Text = "High", Value = "high", Color = "var(--kendo-color-warning)"},
new TaskBoardCardPriority() { Text = "Critical", Value = "critical", Color = "var(--kendo-color-error)" }
};
private void OnTaskBoardColumnCreate(TaskBoardColumnCreateEventArgs<TaskBoardColumn> args)
{
args.Item.Status = $"status-{++LastId}";
TaskBoardColumns.Add(args.Item);
}
private void OnTaskBoardColumnDelete(TaskBoardColumnDeleteEventArgs<TaskBoardColumn> args)
{
TaskBoardColumns.Remove(args.Item);
TaskBoardCards.RemoveAll(c => c.Status == args.Item.Status);
}
private void OnTaskBoardColumnReorder(TaskBoardColumnReorderEventArgs<TaskBoardColumn> args)
{
}
private void OnTaskBoardColumnUpdate(TaskBoardColumnUpdateEventArgs<TaskBoardColumn> args)
{
args.OriginalItem.Title = args.Item.Title;
args.OriginalItem.Status = args.Item.Status;
args.OriginalItem.Width = args.Item.Width;
args.OriginalItem.WipLimit = args.Item.WipLimit;
}
private async Task OnConfirmTaskBoardClick()
{
await JS.InvokeVoidAsync("localStorage.setItem",
new object\\\[] { "taskboard-columns", JsonSerializer.Serialize(TaskBoardColumns) });
await JS.InvokeVoidAsync("localStorage.setItem",
new object\\\[] { "taskboard-priorities", JsonSerializer.Serialize(TaskBoardPriorities) });
}
private readonly string\\\[] PriorityColors = new string\\\[]
{
"var(--kendo-color-info)",
"var(--kendo-color-primary)",
"var(--kendo-color-secondary)",
"var(--kendo-color-tertiary)",
"var(--kendo-color-inverse)",
"var(--kendo-color-success)",
"var(--kendo-color-warning)",
"var(--kendo-color-error)",
"var(--kendo-color-series-b)",
"var(--kendo-color-series-c)",
"var(--kendo-color-series-d)",
"var(--kendo-color-series-f)"
};
private string GetColorKeyword(string themeColorName)
{
return themeColorName.Replace("var(--kendo-color-", "").Replace(")", "");
}
private void OnGridDelete(GridCommandEventArgs args)
{
TaskBoardCardPriority deletedItem = (TaskBoardCardPriority)args.Items.First();
TaskBoardPriorities.Remove(deletedItem);
}
private void OnGridCreate(GridCommandEventArgs args)
{
TaskBoardCardPriority createdItem = (TaskBoardCardPriority)args.Items.First();
TaskBoardPriorities.Insert(0, createdItem);
}
private void OnGridRowDrop(GridRowDropEventArgs<TaskBoardCardPriority> args)
{
TaskBoardPriorities.Remove(args.Item);
int destinationItemIndex = TaskBoardPriorities.IndexOf(args.DestinationItem);
if (args.DropPosition == GridRowDropPosition.After)
{
destinationItemIndex++;
}
TaskBoardPriorities.Insert(destinationItemIndex, args.Item);
}
private void OnGridUpdate(GridCommandEventArgs args)
{
TaskBoardCardPriority updatedItem = (TaskBoardCardPriority)args.Items.First();
int originalItemIndex = TaskBoardPriorities.FindIndex(i => i.Value == updatedItem.Value);
if (originalItemIndex != -1)
{
TaskBoardPriorities\\\[originalItemIndex] = updatedItem;
}
}
private int LastId { get; set; }
public class TaskBoardCard
{
public string Description { get; set; } = string.Empty;
public int Id { get; set; }
public int Index { get; set; }
public string Priority { get; set; } = string.Empty;
public string Status { get; set; } = string.Empty;
public string Title { get; set; } = string.Empty;
}
public class TaskBoardColumn
{
public TaskBoardColumnButtons? Buttons { get; set; }
public bool Enabled { get; set; } = true;
public int Index { get; set; }
public string Status { get; set; } = string.Empty;
public string Title { get; set; } = string.Empty;
public string Width { get; set; } = string.Empty;
public int? WipLimit { get; set; }
}
}
The second example demonstrates the user’s workspace. It loads the column and priority definitions saved by the administrator and then presents a fully operational TelerikTaskBoard where cards can be created, edited, moved and deleted.
Notice that the column management buttons are absent, and the user can only manage cards. The TaskBoardSearchBox tool lets them quickly filter cards by title or description. The task priorities automatically color-code the left border of each card.
@using System.Text.Json
@using Telerik.Blazor.Components.TaskBoard
@inject IJSRuntime JS
<PageTitle>Counter</PageTitle>
<TelerikTaskBoard CardData="@TaskBoardCards"
ColumnData="@TaskBoardColumns"
ColumnReorderable="true"
Height="600px"
Priorities="@TaskBoardPriorities"
OnCardCreate="@OnTaskBoardCardCreate"
OnCardDelete="@OnTaskBoardCardDelete"
OnCardMove="@OnTaskBoardCardMove"
OnCardUpdate="@OnTaskBoardCardUpdate"
TColumn="@TaskBoardColumn"
TItem="@TaskBoardCard">
<TaskBoardSettings>
<TaskBoardColumnSettings Buttons="@(TaskBoardColumnButtons.AddCard)"
Width="300px" />
</TaskBoardSettings>
<TaskBoardToolBar>
<TaskBoardSearchBox />
</TaskBoardToolBar>
</TelerikTaskBoard>
@code {
private List<TaskBoardCard> TaskBoardCards { get; set; } = new List<TaskBoardCard>();
private List<TaskBoardColumn> TaskBoardColumns { get; set; } = new List<TaskBoardColumn>();
private List<TaskBoardCardPriority> TaskBoardPriorities { get; set; } = new List<TaskBoardCardPriority>();
private void OnTaskBoardCardCreate(TaskBoardCardCreateEventArgs<TaskBoardCard> args)
{
args.Item.Id = ++LastId;
// Optionally, add the Card to the bottom of the column
//int maxIndexInColumn = TaskBoardCards.Where(c => c.Status == args.Item.Status).Select(c => c.Index).DefaultIfEmpty(0).Max();
//args.Item.Index = ++maxIndexInColumn;
TaskBoardCards.Add(args.Item);
}
private void OnTaskBoardCardDelete(TaskBoardCardDeleteEventArgs<TaskBoardCard> args)
{
TaskBoardCards.Remove(args.Item);
}
private void OnTaskBoardCardMove(TaskBoardCardMoveEventArgs<TaskBoardCard> args)
{
args.Item.Index = args.NewIndex;
args.Item.Status = args.NewStatus;
}
private void OnTaskBoardCardUpdate(TaskBoardCardUpdateEventArgs<TaskBoardCard> args)
{
args.OriginalItem.Description = args.Item.Description;
args.OriginalItem.Index = args.Item.Index;
args.OriginalItem.Priority = args.Item.Priority;
args.OriginalItem.Title = args.Item.Title;
args.OriginalItem.Status = args.Item.Status;
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
//TaskBoardColumns = await LocalStorage.GetItem<List<TaskBoardColumn>>("taskboard-columns") ?? new List<TaskBoardColumn>();
//TaskBoardPriorities = await LocalStorage.GetItem<List<TaskBoardCardPriority>>("taskboard-priorities") ?? new List<TaskBoardCardPriority>();
string serializedColumns = await JS.InvokeAsync<string>("localStorage.getItem", "taskboard-columns");
TaskBoardColumns = JsonSerializer.Deserialize<List<TaskBoardColumn>>(serializedColumns) ?? new List<TaskBoardColumn>();
string serializedPriorities = await JS.InvokeAsync<string>("localStorage.getItem", "taskboard-priorities");
TaskBoardPriorities = JsonSerializer.Deserialize<List<TaskBoardCardPriority>>(serializedPriorities) ?? new List<TaskBoardCardPriority>();
StateHasChanged();
}
}
private int LastId { get; set; }
public class TaskBoardCard
{
public string Description { get; set; } = string.Empty;
public int Id { get; set; }
public int Index { get; set; }
public string Priority { get; set; } = string.Empty;
public string Status { get; set; } = string.Empty;
public string Title { get; set; } = string.Empty;
}
public class TaskBoardColumn
{
public TaskBoardColumnButtons? Buttons { get; set; }
public bool Enabled { get; set; } = true;
public int Index { get; set; }
public string Status { get; set; } = string.Empty;
public string Title { get; set; } = string.Empty;
public string Width { get; set; } = string.Empty;
public int? WipLimit { get; set; }
public TaskBoardColumn Clone()
{
return new TaskBoardColumn()
{
Status = this.Status,
Title = this.Title,
Width = this.Width,
WipLimit = this.WipLimit
};
}
}
}
The Telerik UI for Blazor TaskBoard fits naturally into enterprise workflows with its data-driven and event-driven architecture. Here are the design principles the examples above demonstrate:
Status, Index, Title, etc.) eliminate boilerplate configuration while still allowing full customization via field-mapping parameters.TaskBoardCardSettings and TaskBoardColumnSettings let you show exactly the right buttons to the right roles.var(--kendo-color-error)) for priority colors enables visual consistency across Telerik themes with zero extra work.TaskBoard and 120 other Telerik UI for Blazor components are all available for a free 30-day trial:
Dimo Dimov is a Support Engineer at Progress, working on Telerik UI for Blazor. He is passionate about fast support service and well-structured documentation. If you succeed in taking the laptop from his hands, you can join him on a mountain hike or a marathon run.