Skip to content
Open source · MIT · net8.0 / net9.0 / net10.0

Sortable lists for Blazor,
dragged in C#.

Reorder items, drag them between lists, clone, swap and multi-drag - with the sorting engine written in C# on the browser's own drag-and-drop events. Not a wrapper around SortableJS: your IList<TItem> is changed in place, so the order a user makes is the order your code reads back.

dotnet add package Bit.Bortable
Backlog.razor
<Bortable @bind-Items="_backlog" TItem="string" Group="board">
    <ItemTemplate Context="task">@task</ItemTemplate>
</Bortable>
Write the spec
Review the PR
Ship it
Celebrate

_backlog: Write the spec, Review the PR, Ship it, Celebrate

Engine in C#where it lands, what moves, what is raised
Your own list@bind-Items changes it in place
SortableJS feature setgroups, clone, swap, multi-drag, thresholds
Every hosting modelWebAssembly, Server, Web App and Hybrid

Capabilities

Everything SortableJS does - in Blazor

The SortableJS feature set and its options, ported to a Blazor component: the same names, the same thresholds, the same plugins - with a strongly typed Bortable<TItem> in front of them.

Live reordering

Items slide out of the way while you drag, with FLIP animations - and the bound list is reordered in place when you drop.

Simple list →

Shared lists

Lists with the same Group exchange items, with a ghost preview of where the item will land - and pull and put rules per list.

Shared lists →

Clone, swap and multi-drag

Copy an item out of a palette, drop one item onto another to trade places, or select several and drag them as one block - the SortableJS plugins, built in.

Multi-drag →

Handles and filters

Limit dragging to a grip so the rest of the row stays clickable, and lock items with a predicate so they cannot be picked up.

Handle →

Swap thresholds

SwapThreshold, InvertSwap and Direction ported from SortableJS, including its guard against items of different sizes swapping back and forth.

Thresholds →

Any layout

Vertical lists, horizontal strips, CSS grids and nested trees whose items move between branches and depths.

Nested sortables →

In practice

See it in code

01

A kanban board is two lists and a group

Give two lists the same Group and their items move between them. Each list stays your own IList, so the board's state is just two fields.

Shared lists →
<Bortable @bind-Items="_todo" TItem="Card" Group="board">…</Bortable>
<Bortable @bind-Items="_done" TItem="Card" Group="board">…</Bortable>
02

A palette clones, a target accepts

Pull="BortablePull.Clone" copies items out and keeps the originals; Sort="false" and Put="false" make the palette read-only.

Cloning →
<Bortable Items="_widgets" TItem="Widget" Group="form"
          Pull="BortablePull.Clone" Sort="false" Put="false">…</Bortable>
03

React to the drop

The SortableJS lifecycle - OnStart, OnAdd, OnRemove, OnUpdate, OnEnd - is raised in C#, with the item, the lists and both indexes in its arguments.

Events →
<Bortable @bind-Items="_done" TItem="Card" OnAdd="Completed">…</Bortable>

private Task Completed(BortableEventArgs<Card> e)
    => api.MarkDoneAsync(e.Item.Id);   // e.FromId, e.OldIndex, e.NewIndex …

Comparison

Why not wrap SortableJS?

A JavaScript wrapper moves DOM nodes behind Blazor's back and then has to reconcile the render tree with what it did. Bortable keeps the decision in C#, so there is nothing to reconcile.

ConcernSortableJS wrapperBortable
Who moves the itemsJavaScript, in the DOMC#, in your list - Blazor renders the result
Bound dataPatched after the fact from DOM indexes@bind-Items, changed in place
Typed itemsIndexes and element idsBortable<TItem> and typed event arguments
Script to shipThe library plus interop glueA small module, imported on demand
Hosting modelsUsually WebAssembly-firstWebAssembly, Server, Web App and Hybrid

Where to start

Choose your path

New to Bortable?

Install, register, link the stylesheet - a sortable list in five minutes.

Getting started →

Want to see it live?

Every feature has a page you can drag around, with its own source one click away.

Open the demos →

Building with an AI agent?

This site is an MCP server: point your agent at /mcp and it reads the shipped library instead of guessing.

Try the MCP server →

Questions

Frequently asked

Is this SortableJS running through JavaScript interop?

No. The sorting engine - where the item goes, which list may take it, what is moved, cloned or swapped, and which events are raised - is C#. A small module does only what needs the DOM: drag handles, measuring the pointer for the swap thresholds, and the FLIP animations.

Which render modes does it work in?

Any interactive one: WebAssembly, Server, Auto and Hybrid, on .NET 8, 9 and 10. Pages are prerendered as usual; the lists become draggable once the page is interactive.

Can the items be anything, not just strings?

Yes - Bortable<TItem> is generic, and ItemTemplate renders whatever you like inside each item. Items are rendered with @key, so they must be unique within a list.

Does it work on touch screens?

It runs on the browser's HTML5 drag-and-drop events, so it drags wherever the browser raises them - every desktop browser, and the mobile browsers that support native drag.

Put your lists in order

One package, one service, one stylesheet - and any list becomes sortable.

dotnet add package Bit.Bortable
An unhandled error has occurred. Reload 🗙