Read More on Telerik Blogs
February 16, 2026 Blazor, Web
Get A Free Trial

See how the Telerik UI for Blazor ListBox makes implementing dragging and dropping items between lists easy—based on a real-life case!

There are applications that require very specific use cases to solve real problems. One such case is the need to implement functionality to drag and drop items between different list-like controls.

This functionality can be found natively in the Blazor ListBox component from Progress Telerik, making it an excellent option in these scenarios, which is why I will show you how to implement drag and drop between several components of this type.

Dragging Items Between ListBoxes in Blazor: A Practical Case

I have to admit that the idea for this post was born from a real-life case, which I will share to provide you with its background. I teach online software development courses, several of which are hosted on a fairly popular platform. This platform offers instructors the convenience of creating coupons using a graphical interface from the dashboard of a particular course.

The problem arises when the instructor has many courses on the platform and wants to create coupons in bulk quickly. The platform has a system based on .csv files to solve this problem, although it is not the best option for someone who is not involved in the technology field.

The first .csv file provided by the platform is a file containing the list of all the instructor’s courses, from which data such as course ID, course name, among other data can be extracted, and it looks like this:

"course_id","course_name","currency","best_price_value","min_custom_price","max_custom_price","coupons_remaining"
1234567,".NET MAUI: Advanced UI and Custom Control Techniques","USD",9.99,12.99,79.99,3

The second .csv file provided is a template to fill in the information for the coupons to be created, in which information such as the discount to be applied, course ID, coupon start date, among other data must be included:

course_id,coupon_type,coupon_code,price,start_date,start_time,custom_price
1234567,best_price,DEVJUN1,9.99,2025-06-16,19:44,

Now, here comes the important part. The data coupon_type specifies whether the coupon is paid or free, specified in four possible values:

  • best_price
  • custom_price
  • free_targeted
  • free_open

Once the csv template is filled with the courses that will have a coupon applied, it can be uploaded on the same portal for the bulk generation of the coupons. The previous process can be quite complicated for instructors not involved in technology, which we can simplify thanks to the use of the Telerik UI for Blazor ListBox component.

Creating a Component Page Using ListBox

The ListBox component is a quite powerful tool that allows you to display lists of selectable, reorderable, deletable items and more. One thing I really like about this component is that you can customize it as much as you want through the tag ItemTemplate.

For our demonstration, I have created a page-type component applying various styles, so you can see the potential of using the ListBox component in action:

@page "/"


<PageTitle>Drag & Drop Demo</PageTitle>

<TelerikRootComponent>
    <div class="demo-container">
        <div class="demo-header">
            <h2>Drag and Drop Demo</h2>
        </div>
        <div class="lists-container">
            <div class="source-column">
                <div class="list-card available-card">
                    <div class="list-header">
                        <h4>Available Courses</h4>
                        <span class="badge">@AvailableCourses.Count</span>
                    </div>
                    <TelerikListBox Data="@AvailableCourses"
                                    TextField="@nameof(Course.Name)"
                                    Height="400px"
                                    Width="100%">
                        <ListBoxToolBarSettings>
                            <ListBoxToolBar Visible="false" />
                        </ListBoxToolBarSettings>
                        <ItemTemplate>
                            @{
                                var course = context;
                            }
                            <div class="course-item">
                                <div class="course-info">
                                    <span class="course-name">@course.Name</span>
                                    <span class="course-price">@course.Currency @course.Price.ToString("N2")</span>
                                </div>
                                <span class="course-category">@course.Category</span>
                            </div>
                        </ItemTemplate>
                    </TelerikListBox>
                </div>
            </div>
            <div class="destination-columns">
                <div class="coupon-grid">                    
                    <div class="list-card best-price-card">
                        <div class="list-header">
                            <h4>Best Price</h4>
                            <span class="badge">@BestPriceCourses.Count</span>
                        </div>
                        <TelerikListBox Data="@BestPriceCourses"
                                        TextField="@nameof(Course.Name)"
                                        Width="100%"
                                        Height="200px">
                            <ListBoxToolBarSettings>
                                <ListBoxToolBar Visible="false" />
                            </ListBoxToolBarSettings>
                            <ItemTemplate>
                                @{
                                    var course = context;
                                }
                                <div class="course-item-mini">
                                    <span class="course-name">@course.Name</span>
                                    <span class="course-tag best-price">@course.Currency @course.Price.ToString("N2")</span>
                                </div>
                            </ItemTemplate>
                            <NoDataTemplate>
                                <div class="no-data">
                                    <span>Drop courses here</span>
                                </div>
                            </NoDataTemplate>
                        </TelerikListBox>
                    </div>
                    
                    <div class="list-card custom-price-card">
                        <div class="list-header">
                            <h4>Custom Price</h4>
                            <span class="badge">@CustomPriceCourses.Count</span>
                        </div>
                        <TelerikListBox Data="@CustomPriceCourses"
                                        TextField="@nameof(Course.Name)"                                        
                                        Width="100%"
                                        Height="200px">
                            <ListBoxToolBarSettings>
                                <ListBoxToolBar Visible="false" />
                            </ListBoxToolBarSettings>
                            <ItemTemplate>
                                @{
                                    var course = context;
                                }
                                <div class="course-item-mini">
                                    <span class="course-name">@course.Name</span>
                                    <span class="course-tag custom-price">@course.Currency @course.Price.ToString("N2")</span>
                                </div>
                            </ItemTemplate>
                            <NoDataTemplate>
                                <div class="no-data">
                                    <span>Drop courses here</span>
                                </div>
                            </NoDataTemplate>
                        </TelerikListBox>
                    </div>
                    
                    <div class="list-card free-open-card">
                        <div class="list-header">
                            <h4>Free Open</h4>
                            <span class="badge">@FreeOpenCourses.Count</span>
                        </div>
                        <TelerikListBox Data="@FreeOpenCourses"
                                        TextField="@nameof(Course.Name)"                                        
                                        Width="100%"
                                        Height="200px">
                            <ListBoxToolBarSettings>
                                <ListBoxToolBar Visible="false" />
                            </ListBoxToolBarSettings>
                            <ItemTemplate>
                                @{
                                    var course = context;
                                }
                                <div class="course-item-mini">
                                    <span class="course-name">@course.Name</span>
                                    <span class="course-tag free">FREE</span>
                                </div>
                            </ItemTemplate>
                            <NoDataTemplate>
                                <div class="no-data">
                                    <span>Drop courses here</span>
                                </div>
                            </NoDataTemplate>
                        </TelerikListBox>
                    </div>
                    
                    <div class="list-card free-targeted-card">
                        <div class="list-header">
                            <h4>Free Targeted</h4>
                            <span class="badge">@FreeTargetedCourses.Count</span>
                        </div>
                        <TelerikListBox Data="@FreeTargetedCourses"
                                        TextField="@nameof(Course.Name)"                                        
                                        Width="100%"
                                        Height="200px">
                            <ListBoxToolBarSettings>
                                <ListBoxToolBar Visible="false" />
                            </ListBoxToolBarSettings>
                            <ItemTemplate>
                                @{
                                    var course = context;
                                }
                                <div class="course-item-mini">
                                    <span class="course-name">@course.Name</span>
                                    <span class="course-tag targeted">TARGETED</span>
                                </div>
                            </ItemTemplate>
                            <NoDataTemplate>
                                <div class="no-data">
                                    <span>Drop courses here</span>
                                </div>
                            </NoDataTemplate>
                        </TelerikListBox>
                    </div>
                </div>
            </div>
        </div>
    </div>
</TelerikRootComponent>

<style>
    :root {
        --bg-color: #1e1e1e;
        --bg-accent: #252526;
        --card-glass: #252526;
        --border-glass: #3c3c3c;
        --accent-blue: #4ec9b0;
        --accent-purple: #c586c0;
        --accent-green: #608b4e;
        --accent-amber: #d7ba7d;
        --accent-red: #f48771;
        --text-main: #d4d4d4;
        --text-muted: #858585;
        --shadow-soft: 0 8px 16px rgba(0, 0, 0, 0.45);
        --radius-lg: 10px;
        --radius-md: 8px;
        --transition-fast: 0.15s ease-out;
    }

    .demo-container {
        min-height: calc(100vh - 40px);
        max-width: 1440px;
        margin: 0 auto;
        padding: 24px 20px 32px;
        font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
        color: var(--text-main);
        position: relative;
        background-color: var(--bg-color);
    }

        .demo-container::before {
            content: "";
            position: fixed;
            inset: 0;
            background: var(--bg-color);
            z-index: -2;
        }

        .demo-container::after {
            content: "";
            position: fixed;
            inset: 0;
            background: transparent;
            z-index: -1;
            pointer-events: none;
        }

    .demo-header {
        display: flex;
        flex-direction: column;
        align-items: center;
        gap: 6px;
        margin-bottom: 22px;
        text-align: center;
    }

        .demo-header h2 {
            font-size: 1.9rem;
            letter-spacing: 0.02em;
            font-weight: 650;
            color: var(--text-main);
        }

    .demo-subtitle {
        font-size: 0.95rem;
        color: var(--text-muted);
        max-width: 620px;
    }

    .lists-container {
        display: grid;
        grid-template-columns: minmax(320px, 360px) minmax(0, 1fr);
        gap: 18px;
        align-items: stretch;
    }

    .source-column,
    .destination-columns {
        position: relative;
        z-index: 1;
    }

    .list-card {
        border-radius: var(--radius-lg);
        background-color: var(--card-glass);
        border: 1px solid var(--border-glass);
        box-shadow: none;
        backdrop-filter: none;
        -webkit-backdrop-filter: none;
        overflow: hidden;
        transition: border-color var(--transition-fast), background-color var(--transition-fast);
    }

    .list-header {
        display: flex;
        align-items: center;
        gap: 10px;
        padding: 12px 16px 10px;
        border-bottom: 1px solid rgba(148, 163, 184, 0.3);
    }

        .list-header h4 {
            margin: 0;
            flex: 1;
            font-size: 0.92rem;
            text-transform: uppercase;
            letter-spacing: 0.08em;
            color: #e5e7eb;
        }

    .badge {
        background-color: #333333;
        color: var(--text-main);
        padding: 2px 9px;
        border-radius: 999px;
        font-size: 0.74rem;
        font-weight: 600;
        border: 1px solid var(--border-glass);
        box-shadow: none;
    }

    .coupon-grid {
        display: grid;
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: 14px;
    }

    .course-item {
        padding: 10px 12px 8px;
        display: flex;
        flex-direction: column;
        gap: 4px;
    }

    .course-info {
        display: flex;
        justify-content: space-between;
        align-items: center;
        gap: 10px;
    }

    .course-name {
        font-weight: 600;
        color: #2d2d30;
        font-size: 0.95rem;
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
    }

    .course-price {
        font-weight: 600;
        color: var(--accent-green);
        font-size: 0.86rem;
    }

    .course-category {
        font-size: 0.8rem;
        color: #6e6e6e;
    }

    :deep(.k-listbox) {
        border: none !important;
        background: transparent;
        color: var(--text-main);
    }

    :deep(.k-listbox .k-list-content) {
        border-radius: var(--radius-md);
        margin: 8px;
        background-color: #1e1e1e;
        border: 1px solid var(--border-glass);
    }

    :deep(.k-listbox .k-list-item) {
        border-radius: 4px;
        margin: 2px 3px;
        transition: background-color var(--transition-fast);
        color: var(--text-main);
    }

    :deep(.k-listbox .k-list-item:hover) {
        background-color: #2d2d30;
    }

    :deep(.k-listbox .k-list-item.k-selected) {
        background-color: var(--accent-blue);
        color: #1e1e1e;
        box-shadow: none;
    }

    :deep(.k-listbox .k-list-item.k-selected .course-name),
    :deep(.k-listbox .k-list-item.k-selected .course-price),
    :deep(.k-listbox .k-list-item.k-selected .course-category) {
        color: #1e1e1e;
    }

    @@media (max-width: 1200px) {
        .lists-container {
            grid-template-columns: minmax(0, 1fr);
        }

        .coupon-grid {
            grid-template-columns: repeat(2, minmax(0, 1fr));
        }
    }

    @@media (max-width: 768px) {
        .demo-container {
            padding-inline: 14px;
        }

        .lists-container {
            grid-template-columns: minmax(0, 1fr);
            gap: 14px;
        }

        .coupon-grid {
            grid-template-columns: minmax(0, 1fr);
        }
    }
</style>

@code {
    public class Course
    {
        public int Id { get; set; }
        public string Name { get; set; } = string.Empty;
        public string Category { get; set; } = string.Empty;
        public decimal Price { get; set; }
        public string Currency { get; set; } = "USD";
    }

    private List<Course> AvailableCourses { get; set; } = new();
    private List<Course> BestPriceCourses { get; set; } = new();
    private List<Course> CustomPriceCourses { get; set; } = new();
    private List<Course> FreeOpenCourses { get; set; } = new();
    private List<Course> FreeTargetedCourses { get; set; } = new();

    protected override void OnInitialized()
    {
        LoadSampleData();
    }

    private void LoadSampleData()
    {
        AvailableCourses = new List<Course>
        {
            new() { Id = 1, Name = "Blazor Fundamentals", Category = "Web Development", Price = 19.99m, Currency = "USD" },
            new() { Id = 2, Name = "C# Advanced Patterns", Category = "Programming", Price = 24.99m, Currency = "USD" },
            new() { Id = 3, Name = "ASP.NET Core API", Category = "Backend", Price = 29.99m, Currency = "USD" },
            new() { Id = 4, Name = "Entity Framework Core", Category = "Database", Price = 14.99m, Currency = "USD" },
            new() { Id = 5, Name = "Azure DevOps Pipeline", Category = "DevOps", Price = 34.99m, Currency = "USD" },
            new() { Id = 6, Name = "Docker & Kubernetes", Category = "Infrastructure", Price = 39.99m, Currency = "USD" },
            new() { Id = 7, Name = "React with TypeScript", Category = "Frontend", Price = 22.99m, Currency = "USD" },
            new() { Id = 8, Name = "SQL Server Mastery", Category = "Database", Price = 27.99m, Currency = "USD" },
            new() { Id = 9, Name = "Git & GitHub Pro", Category = "Tools", Price = 12.99m, Currency = "USD" },
            new() { Id = 10, Name = "Machine Learning Basics", Category = "AI/ML", Price = 44.99m, Currency = "USD" }
        };
        BestPriceCourses = new();
        CustomPriceCourses = new();
        FreeOpenCourses = new();
        FreeTargetedCourses = new();
    }
}

In the code above, you can see that we used several TelerikListBox tags to render the ListBox components, as well as the use of ItemTemplate to apply styles to each item within the ListBox.

Similarly, I have created a class Course that represents a course, which is useful for filling and binding the lists to each ListBox through the Data parameter of the component to display the information, as shown below:

The result is fascinating and undoubtedly demonstrates the potential of the component.

Enabling Drag & Drop Functionality

The TelerikListBox component has several parameters that we need to configure so that we can drag and drop items between ListBoxes, following these steps. It is worth mentioning that I will show you the configuration for just one, although the implementation for the others is similar.

  1. Each of the ListBoxes that will be used to transfer items must have an id of type string, which should be set in the Id parameter. This is essential for knowing from which component the item is coming and where it is going. An example of this configuration is shown below:
<TelerikListBox Data="@AvailableCourses"
                Id="@ListBoxIdAvailable"
                ...>
    ...
</TelerikListBox>
@code {
    ...
    private const string ListBoxIdAvailable = "availableCourses";
    ...
}
  1. The second step is to create @ref references, which allows invoking the Rebind method to carry out a data update every time we move items between the lists:
<TelerikListBox Data="@AvailableCourses"
                @ref="@ListBoxRefAvailable"
                ...>
    ...
</TelerikListBox>
@code {
    ...
    private TelerikListBox<Course> ListBoxRefAvailable { get; set; } = null!;
    ...
}
  1. You must be able to obtain the selected elements from each ListBox, which is possible by creating a list for each ListBox and binding them through the SelectedItems property. Also, make sure to specify the appropriate SelectionMode type, as well as assign the SelectedItemsChanged parameter to update these lists:
<TelerikListBox Data="@AvailableCourses"
                SelectionMode="@ListBoxSelectionMode.Multiple"
                SelectedItems="@SelectedItemsAvailable"
                SelectedItemsChanged="@((IEnumerable<Course> items) => OnSelectedItemsChanged(items, ListBoxIdAvailable))"
                ...>
    ...
</TelerikListBox>
@code {
    ...
    private IEnumerable<Course> SelectedItemsAvailable { get; set; } = new List<Course>();
    private IEnumerable<Course> SelectedItems1 { get; set; } = new List<Course>();
    private IEnumerable<Course> SelectedItems2 { get; set; } = new List<Course>();
    private IEnumerable<Course> SelectedItems3 { get; set; } = new List<Course>();
    private IEnumerable<Course> SelectedItems4 { get; set; } = new List<Course>();
    ...
    private void OnSelectedItemsChanged(IEnumerable<Course> items, string listBoxId)
    {
        switch (listBoxId)
        {
            case ListBoxIdAvailable:
                SelectedItemsAvailable = items;
                break;
            case ListBoxId1:
                SelectedItems1 = items;
                break;
            case ListBoxId2:
                SelectedItems2 = items;
                break;
            case ListBoxId3:
                SelectedItems3 = items;
                break;
            case ListBoxId4:
                SelectedItems4 = items;
                break;
        }
    }        
    ...
}
  1. You must assign a true value to the Draggable parameter, indicating that the ListBoxes will be able to send and receive items between them.
<TelerikListBox Data="@AvailableCourses"
                Draggable="true"
                ...>
    ...
</TelerikListBox>
  1. Enable the DropSources parameter, which is a list of Ids of the ListBoxes.
<TelerikListBox Data="@AvailableCourses"
                DropSources="@ListBoxDropSources"
                ...>
    ...
</TelerikListBox>
@code {
    ...
    private List<string> ListBoxDropSources => new()
    {
        ListBoxIdAvailable,
        ListBoxId1,
        ListBoxId2,
        ListBoxId3,
        ListBoxId4
    };
    ...
}
  1. Specify the OnDrop parameter, which is an EventCallback that is triggered when a drag-and-drop interaction occurs, expecting a delegate that receives an argument ListBoxDropEventArgs<T>. This argument contains a property Items with the elements being moved between ListBoxes, a DestinationIndex that represents the index within the destination ListBox where the item was dropped, and finally DestinationListBoxId, which allows knowing in which ListBox we dropped the items. Additionally, we can add information like the Id of the ListBox from which the operation is being performed:
<TelerikListBox Data="@AvailableCourses"
                OnDrop="@((ListBoxDropEventArgs<Course> args) => OnDropHandler(args, ListBoxIdAvailable))"
                ...>
    ...
</TelerikListBox>
  1. Create the event handler where the processing takes place when the items are dropped. This method is the most complex, but I will explain the main parts:
@code {
    ...
    private void OnDropHandler(ListBoxDropEventArgs<Course> args, string sourceListBoxId)
    {
        // A. 
        var draggedItem = args.Items?.FirstOrDefault();
        if (draggedItem == null) return;                    
        if (string.IsNullOrEmpty(sourceListBoxId)) return;
        var destinationId = args.DestinationListBoxId;
        var destinationIndex = args.DestinationIndex ?? 0;        
        var sourceList = GetListById(sourceListBoxId);
        var destinationList = GetListById(destinationId);
        if (sourceList == null || destinationList == null) return;        
        var selectedItems = GetSelectedItemsFromListBox(sourceListBoxId);

        // B.
        List<Course> itemsToMove;
        if (selectedItems.Any() && selectedItems.Any(item => item.Id == draggedItem.Id))
        {
            itemsToMove = selectedItems.ToList();
        }
        else
        {
            itemsToMove = args.Items.ToList();
        }

        // C. 
        if (destinationId == sourceListBoxId)
        {            
            ReorderItems(itemsToMove, sourceList, destinationIndex);
        }
        else
        {            
            MoveItems(itemsToMove, sourceList, destinationList, destinationIndex);            
            ClearSelectedItems(sourceListBoxId);
        }
        
        RebindAllListBoxes();
    }
    ...
}

A. The variables draggedItem, destinationId, destinationIndex, sourceList, destinationList and selectedItems are created, which will serve to perform different operations such as validations, reordering, etc.

B. The variable itemsToMove is created, which will establish which items will be moved. The check allows specifying which items will be moved, whether the selected ones or some different ones. For example, suppose you select the first two items, and for some reason, you prefer not to move them, but instead, you prefer to move Item 3. In this case, only Item 3 will be moved and not the selected ones.

C. The if allows knowing if the movement of items has been made within the same list or if an attempt is being made to move between different lists. If it is within the same list, reordering is done according to the destinationIndex. If the transfer is made between lists, the items are moved between the lists and deselected. Finally, a Rebind is carried out to update any pending interface changes.

Lastly, I show you the helper methods used:

@code {
    ...    
    private List<Course>? GetListById(string listBoxId)
    {
        return listBoxId switch
        {
            ListBoxIdAvailable => AvailableCourses,
            ListBoxId1 => BestPriceCourses,
            ListBoxId2 => CustomPriceCourses,
            ListBoxId3 => FreeOpenCourses,
            ListBoxId4 => FreeTargetedCourses,
            _ => null
        };
    }

    private IEnumerable<Course> GetSelectedItemsFromListBox(string listBoxId)
    {
        return listBoxId switch
        {
            ListBoxIdAvailable => SelectedItemsAvailable ?? new List<Course>(),
            ListBoxId1 => SelectedItems1 ?? new List<Course>(),
            ListBoxId2 => SelectedItems2 ?? new List<Course>(),
            ListBoxId3 => SelectedItems3 ?? new List<Course>(),
            ListBoxId4 => SelectedItems4 ?? new List<Course>(),
            _ => new List<Course>()
        };
    }

    private void ReorderItems(List<Course> items, List<Course> collection, int destinationIndex)
    {        
        foreach (var item in items)
        {
            collection.RemoveAll(x => x.Id == item.Id);
        }
        
        if (destinationIndex >= 0 && destinationIndex <= collection.Count)
        {
            var index = destinationIndex;
            foreach (var item in items)
            {
                collection.Insert(index, item);
                index++;
            }
        }
        else
        {
            collection.AddRange(items);
        }
    }    

    private void MoveItems(List<Course> items, List<Course> sourceList, List<Course> destinationList, int destinationIndex)
    {        
        foreach (var item in items)
        {
            sourceList.RemoveAll(x => x.Id == item.Id);
        }
        
        var insertIndex = destinationIndex >= 0 && destinationIndex <= destinationList.Count
            ? destinationIndex
            : destinationList.Count;

        foreach (var item in items)
        {
            destinationList.Insert(insertIndex, item);
            insertIndex++;
        }
    }

    private void ClearSelectedItems(string listBoxId)
    {
        switch (listBoxId)
        {
            case ListBoxIdAvailable:
                SelectedItemsAvailable = new List<Course>();
                break;
            case ListBoxId1:
                SelectedItems1 = new List<Course>();
                break;
            case ListBoxId2:
                SelectedItems2 = new List<Course>();
                break;
            case ListBoxId3:
                SelectedItems3 = new List<Course>();
                break;
            case ListBoxId4:
                SelectedItems4 = new List<Course>();
                break;
        }
    }    

    private void RebindAllListBoxes()
    {
        ListBoxRefAvailable?.Rebind();
        ListBoxRef1?.Rebind();
        ListBoxRef2?.Rebind();
        ListBoxRef3?.Rebind();
        ListBoxRef4?.Rebind();
    }
}

With the above code implemented, we now have drag and drop on the page, working with both individual elements and multiple elements as shown below:

With this, we have finished implementing the drag-and-drop functionality. Finally, we could obtain the list of courses in each of the bound lists in the ListBoxes to get their classification, thus generating a final CSV file to create coupons in bulk easily, which greatly simplifies coupon generation.

Conclusion

The creation of applications should always be oriented toward enabling users to solve their problems without worrying about performing complex steps.

In this article, we have seen how to solve a real-world problem through the implementation of drag and drop using the TelerikListBox component. You have learned the steps involved in enabling it and some methods that can help you carry out this implementation more easily. I hope this is very helpful to you and that at some point you can implement it to assist your users.

Ready to try out this component and 120 others in the Telerik UI for Blazor library? The trial is free for 30 days!

Try Now


About the Author

Héctor Pérez

Héctor Pérez is a Microsoft MVP with more than 10 years of experience in software development. He is an independent consultant, working with business and government clients to achieve their goals. Additionally, he is an author of books and an instructor at El Camino Dev and Devs School.

 

Related Posts