Blazor Sortable List
The Blazor Bootstrap Sortable List component, built on top of SortableJS, enables drag-and-drop reordering of lists.
Setup #
Before using the SortableList component, include the SortableJS script reference in your
index.html/_Host.cshtml
file.<script src="https://cdn.jsdelivr.net/npm/sortablejs@latest/Sortable.min.js"></script>
Examples #
Employee 1
Employee 2
Employee 3
Employee 4
Employee 5
<SortableList TItem="Employee"
Data="employees"
Context="item"
OnUpdate="@OnEmployeeListUpdate">
<ItemTemplate>
@item.Name
</ItemTemplate>
</SortableList>
@code {
public List<Employee> employees = Enumerable.Range(1, 5).Select(i => new Employee(i, $"Employee {i}")).ToList();
private void OnEmployeeListUpdate(SortableListEventArgs args)
{
var itemToMove = employees[args.OldIndex];
employees.RemoveAt(args.OldIndex);
if (args.NewIndex < employees.Count)
employees.Insert(args.NewIndex, itemToMove);
else
employees.Add(itemToMove);
}
public record Employee(int Id, string? Name);
}
Shared lists #
To drag-and-drop an item from one list to the other and vice versa, set the Group parameter for all the lists. Providing the same Group name for the lists is what links them together.
In the below example, both lists use the same Group.
Employee 1
Employee 2
Employee 3
Employee 4
Employee 5
Employee 6
Employee 7
Employee 8
Employee 9
Employee 10
<div class="row">
<div class="col">
<SortableList TItem="Employee"
Group="SharedListExample2"
Name="empList1"
Data="employeeList1"
Context="item"
OnUpdate="OnEmployeeList1Update"
OnRemove="OnEmployeeList1Remove">
<ItemTemplate>
@item.Name
</ItemTemplate>
</SortableList>
</div>
<div class="col">
<SortableList TItem="Employee"
Group="SharedListExample2"
Name="empList2"
Data="employeeList2"
Context="item"
OnUpdate="OnEmployeeList2Update"
OnRemove="OnEmployeeList2Remove">
<ItemTemplate>
@item.Name
</ItemTemplate>
</SortableList>
</div>
</div>
@code {
public List<Employee> employeeList1 = Enumerable.Range(1, 5).Select(i => new Employee(i, $"Employee {i}")).ToList();
public List<Employee> employeeList2 = Enumerable.Range(6, 5).Select(i => new Employee(i, $"Employee {i}")).ToList();
private void OnEmployeeList1Update(SortableListEventArgs args)
{
var itemToMove = employeeList1[args.OldIndex];
employeeList1.RemoveAt(args.OldIndex);
if (args.NewIndex < employeeList1.Count)
employeeList1.Insert(args.NewIndex, itemToMove);
else
employeeList1.Add(itemToMove);
}
private void OnEmployeeList2Update(SortableListEventArgs args)
{
var itemToMove = employeeList2[args.OldIndex];
employeeList2.RemoveAt(args.OldIndex);
if (args.NewIndex < employeeList2.Count)
employeeList2.Insert(args.NewIndex, itemToMove);
else
employeeList2.Add(itemToMove);
}
private void OnEmployeeList1Remove(SortableListEventArgs args)
{
// get the item at the old index in list 1
var item = employeeList1[args.OldIndex];
// add it to the new index in list 2
employeeList2.Insert(args.NewIndex, item);
// remove the item from the old index in list 1
employeeList1.Remove(employeeList1[args.OldIndex]);
}
private void OnEmployeeList2Remove(SortableListEventArgs args)
{
// get the item at the old index in list 2
var item = employeeList2[args.OldIndex];
// add it to the new index in list 1
employeeList1.Insert(args.NewIndex, item);
// remove the item from the old index in list 2
employeeList2.Remove(employeeList2[args.OldIndex]);
}
public record Employee(int Id, string? Name);
}
In the following example, all three lists use the same group.
Employee 10
Employee 11
Employee 12
Employee 13
Employee 14
Employee 20
Employee 21
Employee 22
Employee 23
Employee 24
Employee 30
Employee 31
Employee 32
Employee 33
Employee 34
<div class="row">
<div class="col">
<SortableList TItem="Employee"
Group="SharedListExample3"
Name="empList1"
Data="employeeList1"
Context="item"
OnUpdate="OnEmployeeList1Update"
OnRemove="OnEmployeeListRemove">
<ItemTemplate>
@item.Name
</ItemTemplate>
</SortableList>
</div>
<div class="col">
<SortableList TItem="Employee"
Group="SharedListExample3"
Name="empList2"
Data="employeeList2"
Context="item"
OnUpdate="OnEmployeeList2Update"
OnRemove="OnEmployeeListRemove">
<ItemTemplate>
@item.Name
</ItemTemplate>
</SortableList>
</div>
<div class="col">
<SortableList TItem="Employee"
Group="SharedListExample3"
Name="empList3"
Data="employeeList3"
Context="item"
OnUpdate="OnEmployeeList3Update"
OnRemove="OnEmployeeListRemove">
<ItemTemplate>
@item.Name
</ItemTemplate>
</SortableList>
</div>
</div>
@code {
public List<Employee> employeeList1 = Enumerable.Range(10, 5).Select(i => new Employee(i, $"Employee {i}")).ToList();
public List<Employee> employeeList2 = Enumerable.Range(20, 5).Select(i => new Employee(i, $"Employee {i}")).ToList();
public List<Employee> employeeList3 = Enumerable.Range(30, 5).Select(i => new Employee(i, $"Employee {i}")).ToList();
private void OnEmployeeList1Update(SortableListEventArgs args)
{
var itemToMove = employeeList1[args.OldIndex];
employeeList1.RemoveAt(args.OldIndex);
if (args.NewIndex < employeeList1.Count)
employeeList1.Insert(args.NewIndex, itemToMove);
else
employeeList1.Add(itemToMove);
}
private void OnEmployeeList2Update(SortableListEventArgs args)
{
var itemToMove = employeeList2[args.OldIndex];
employeeList2.RemoveAt(args.OldIndex);
if (args.NewIndex < employeeList2.Count)
employeeList2.Insert(args.NewIndex, itemToMove);
else
employeeList2.Add(itemToMove);
}
private void OnEmployeeList3Update(SortableListEventArgs args)
{
var itemToMove = employeeList3[args.OldIndex];
employeeList3.RemoveAt(args.OldIndex);
if (args.NewIndex < employeeList3.Count)
employeeList3.Insert(args.NewIndex, itemToMove);
else
employeeList3.Add(itemToMove);
}
private void OnEmployeeListRemove(SortableListEventArgs args)
{
Employee? item = default!;
// get the item at the old index
if (args.FromListName == "empList1")
item = employeeList1[args.OldIndex];
else if (args.FromListName == "empList2")
item = employeeList2[args.OldIndex];
else
item = employeeList3[args.OldIndex];
// add it to the new index
if (args.ToListName == "empList1")
employeeList1.Insert(args.NewIndex, item);
else if (args.ToListName == "empList2")
employeeList2.Insert(args.NewIndex, item);
else
employeeList3.Insert(args.NewIndex, item);
// remove the item from the old index
if (args.FromListName == "empList1")
employeeList1.Remove(employeeList1[args.OldIndex]);
else if (args.FromListName == "empList2")
employeeList2.Remove(employeeList2[args.OldIndex]);
else
employeeList3.Remove(employeeList3[args.OldIndex]);
}
public record Employee(int Id, string? Name);
}
Cloning #
By setting
Pull="SortableListPullMode.Clone"
, you can enable item cloning. Drag an item from one list to another to create a copy that stays in the original list.
Employee 10
Employee 11
Employee 12
Employee 13
Employee 14
Employee 20
Employee 21
Employee 22
Employee 23
Employee 24
<div class="row">
<div class="col">
<SortableList TItem="Employee"
Data="employeeList1"
Context="item"
Group="SharedListExample3"
Pull="SortableListPullMode.Clone"
OnUpdate="OnEmployeeList1Update"
OnRemove="OnEmployeeList1Remove">
<ItemTemplate>
@item.Name
</ItemTemplate>
</SortableList>
</div>
<div class="col">
<SortableList TItem="Employee"
Data="employeeList2"
Context="item"
Group="SharedListExample3"
Pull="SortableListPullMode.Clone"
OnUpdate="OnEmployeeList2Update"
OnRemove="OnEmployeeList2Remove">
<ItemTemplate>
@item.Name
</ItemTemplate>
</SortableList>
</div>
</div>
@code {
public List<Employee> employeeList1 = Enumerable.Range(10, 5).Select(i => new Employee(i, $"Employee {i}")).ToList();
public List<Employee> employeeList2 = Enumerable.Range(20, 5).Select(i => new Employee(i, $"Employee {i}")).ToList();
private void OnEmployeeList1Update(SortableListEventArgs args)
{
var itemToMove = employeeList1[args.OldIndex];
employeeList1.RemoveAt(args.OldIndex);
if (args.NewIndex < employeeList1.Count)
employeeList1.Insert(args.NewIndex, itemToMove);
else
employeeList1.Add(itemToMove);
}
private void OnEmployeeList2Update(SortableListEventArgs args)
{
var itemToMove = employeeList2[args.OldIndex];
employeeList2.RemoveAt(args.OldIndex);
if (args.NewIndex < employeeList2.Count)
employeeList2.Insert(args.NewIndex, itemToMove);
else
employeeList2.Add(itemToMove);
}
private void OnEmployeeList1Remove(SortableListEventArgs args)
{
// get the item at the old index in list 1
var item = employeeList1[args.OldIndex];
var clone = item with {};
// add it to the new index in list 2
employeeList2.Insert(args.NewIndex, clone);
}
private void OnEmployeeList2Remove(SortableListEventArgs args)
{
// get the item at the old index in list 2
var item = employeeList2[args.OldIndex];
var clone = item with { };
// add it to the new index in list 1
employeeList1.Insert(args.NewIndex, clone);
}
public record Employee(int Id, string? Name);
}
Disable sorting #
You can disable list sorting by setting
AllowSorting="false"
. In the example below, the list cannot be sorted.
Item 1
Item 2
Item 3
Item 4
Item 5
<SortableList TItem="Employee"
Data="items"
Context="item"
AllowSorting="false">
<ItemTemplate>
@item.Name
</ItemTemplate>
</SortableList>
@code {
public List<Employee> items = Enumerable.Range(1, 5).Select(i => new Employee { Id = i, Name = $"Item {i}" }).ToList();
public class Employee
{
public int Id { get; set; }
public string? Name { get; set; }
}
}
Handle #
The Handle parameter specifies the CSS class that denotes the drag handle. In the example below, items can only be sorted by dragging the handle itself.
Employee 1
Employee 2
Employee 3
Employee 4
Employee 5
<SortableList Class="mb-3"
Handle=".bb-sortable-list-handle"
TItem="Employee"
Data="employees"
Context="item"
OnUpdate="@OnEmployeeListUpdate">
<ItemTemplate>
<div class="d-flex justify-content-start">
<div class="bb-sortable-list-handle pe-2"><Icon Name="IconName.GripVertical" /></div>
<div>@item.Name</div>
</div>
</ItemTemplate>
</SortableList>
@code {
public List<Employee> employees = Enumerable.Range(1, 5).Select(i => new Employee(i, $"Employee {i}")).ToList();
private void OnEmployeeListUpdate(SortableListEventArgs args)
{
var itemToMove = employees[args.OldIndex];
employees.RemoveAt(args.OldIndex);
if (args.NewIndex < employees.Count)
employees.Insert(args.NewIndex, itemToMove);
else
employees.Add(itemToMove);
}
public record Employee(int Id, string? Name);
}
Disable item #
Try dragging the red-backgrounded item. You won't be able to, as it's disabled using the DisableItem parameter.
Employee 1
Employee 2
Employee 3
Employee 4
Employee 5
<SortableList TItem="Employee"
Data="employees"
Context="item"
DisableItem="(emp) => emp.Id == 4"
DisabledItemCssClass="list-group-item-danger border-0"
OnUpdate="@OnEmployeeListUpdate">
<ItemTemplate>
@item.Name
</ItemTemplate>
</SortableList>
@code {
public List<Employee> employees = Enumerable.Range(1, 5).Select(i => new Employee(i, $"Employee {i}")).ToList();
private void OnEmployeeListUpdate(SortableListEventArgs args)
{
var itemToMove = employees[args.OldIndex];
employees.RemoveAt(args.OldIndex);
if (args.NewIndex < employees.Count)
employees.Insert(args.NewIndex, itemToMove);
else
employees.Add(itemToMove);
}
public record Employee(int Id, string? Name);
}
Nested sortables #
Nested list sorting is not currently supported. We will add this feature in upcoming releases.
Dynamic data #
No records to display
<SortableList Class="mb-3"
TItem="Employee"
Data="employees"
Context="item"
IsLoading="isLoading"
OnUpdate="@OnEmployeeListUpdate">
<ItemTemplate>
@item.Name
</ItemTemplate>
</SortableList>
<Button Color="ButtonColor.Success" @onclick="LoadDataAsync"> Load data </Button>
@code {
public bool isLoading = false;
public List<Employee> employees = null!;
private async Task LoadDataAsync()
{
isLoading = true;
await Task.Delay(3000);
employees = Enumerable.Range(1, 5).Select(i => new Employee(i, $"Employee {i}")).ToList();
isLoading = false;
await base.OnInitializedAsync();
}
private void OnEmployeeListUpdate(SortableListEventArgs args)
{
var itemToMove = employees[args.OldIndex];
employees.RemoveAt(args.OldIndex);
if (args.NewIndex < employees.Count)
employees.Insert(args.NewIndex, itemToMove);
else
employees.Add(itemToMove);
}
public record Employee(int Id, string? Name);
}
Empty data #
No records to display
<SortableList TItem="Employee"
Data="items"
Context="item">
<ItemTemplate>
@item.Name
</ItemTemplate>
</SortableList>
@code {
public List<Employee> items = null!;
public record Employee(int Id, string? Name);
}