Skip to main content

Blazor Sortable List (Preview)

The Blazor Bootstrap Sortable List component, built on top of SortableJS, enables drag-and-drop reordering of lists.

Blazor Sortable List

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>

Parameters

NameTypeDefaultRequiredDescriptionAdded Version
AllowSortingbooltrueGets or sets a value indicating whether sorting is allowed for the list.2.2.0
ChildContentRenderFragmentnull✔️Gets or sets the content to be rendered within the component.2.2.0
DataList<TItem>nullGets or sets the items.2.2.0
DisabledItemCssClassstring?nullGets or sets the CSS class applied to disabled items.2.2.0
DisableItemFunc<TItem, bool>Gets or sets a delegate that determines whether an item should be disabled.2.2.0
EmptyDataTemplateRenderFragmentnullSpecifies the template to render when there are no items to display in the list.2.2.0
EmptyTextstringNo records to displayGets or sets the text to display when there are no records in the list.2.2.0
Groupstring?nullGets or sets the group name associated with the list.2.2.0
Handlestring?nullGets or sets the CSS selector for the drag handle element.2.2.0
IsLoadingboolfalseGets or sets a value indicating whether the list is currently loading.2.2.0
ItemTemplateRenderFragment<TItem>?nullGets or sets the template used to render individual items in the list.2.2.0
LoadingTemplateRenderFragmentnullGets or sets the loading template.2.2.0
Namestring?nullGets or sets the name of the SortableList component.2.2.0
PullSortableListPullModeSortableListPullMode.TrueGets or sets the pull mode for the sortable list.2.2.0
PutSortableListPutModeSortableListPutMode.TrueGets or sets the put mode for the sortable list.2.2.0

Methods

There are no public methods available.

Callback Events

EventDescriptionAdded Version
OnAddGets or sets an event callback that fires when an item is added to the list.2.2.0
OnRemoveGets or sets an event callback that fires when an item is removed from the list.2.2.0
OnUpdateGets or sets an event callback that fires when an item is updated in the list.2.2.0

Examples

Basic usage

Blazor Sortable List - Basic usage
<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);
}

See demo here.

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.

Blazor Sortable List - Shared lists
<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.

Blazor Sortable List - Shared lists - More than two lists
<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);
}

See demo here.

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.

Blazor Sortable List - Cloning
<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);
}

See demo here.

Disable sorting

You can disable list sorting by setting AllowSorting="false". In the example below, the list cannot be sorted.

Blazor Sortable List - Disable sorting
<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; }
}
}

See demo here.

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.

Blazor Sortable List - Handle
<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);
}

See demo here.

Disable item

Try dragging the red-backgrounded item. You won't be able to, as it's disabled using the DisableItem parameter.

Blazor Sortable List - Disable item
<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);
}

See demo here.

Nested sortables

note

Nested list sorting is not currently supported. We will add this feature in upcoming releases.

Dynamic data

Blazor Sortable List - Dynamic data
<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);
}

See demo here.

Empty data

Blazor Sortable List - Empty data
<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);
}

See demo here.