Telerik blogs

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.

What Is the Telerik UI for Blazor TaskBoard?

The TelerikTaskBoard component displays task and progress information as Cards organized in Columns. The component supports:

  • Flexible data binding with convention-based or custom property names
  • Drag-and-drop for Cards and Columns
  • Built-in and custom toolbar tools
  • CRUD operations for Cards and Columns
  • A rich event model
  • Card priorities with color coding
  • Column-level restrictions about the maximum allowed number of Cards
  • Customizable templates for Cards and Column headers

Data Binding

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.

Card Model

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; } 
} 

Column Model

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

Drag-and-drop is the signature UX feature of any Kanban board, and the TaskBoard delivers it with minimal setup.

Dragging Cards

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; 
    } 
} 

Reordering Columns

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 
    } 
} 

Editing Cards and Columns

The TaskBoard supports built-in create, edit and delete operations for both Cards and Columns.

Card CRUD Events

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 CRUD Events

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; 
    } 
} 

ToolBar

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> 

Two-Role TaskBoard Use Case

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.

Administrator Role: Configuring the Board Columns

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; } 
    } 
} 

User Role: Working with Tasks

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 
            }; 
        } 
    } 
} 

Key Takeaways

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:

  • Separation of concerns – Board structure (columns, priorities) lives apart from the task data, enabling role-based access and centralized configuration.
  • Convention-based models – Default property names (Status, Index, Title, etc.) eliminate boilerplate configuration while still allowing full customization via field-mapping parameters.
  • Event-sourced mutations – Every user action fires a cancellable event. Your handlers decide what gets persisted and how, making integration with any backend straightforward.
  • Progressive disclosureTaskBoardCardSettings and TaskBoardColumnSettings let you show exactly the right buttons to the right roles.
  • Theme-aware colors – Using CSS theme variables (var(--kendo-color-error)) for priority colors enables visual consistency across Telerik themes with zero extra work.

Resources

Ready to Experiment with the TaskBoard Control?

TaskBoard and 120 other Telerik UI for Blazor components are all available for a free 30-day trial:

Try Now


About the Author

Dimo Dimov

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.

Related Posts

Comments

Comments are disabled in preview mode.