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
<Bortable @bind-Items="_backlog" TItem="string" Group="board"> <ItemTemplate Context="task">@task</ItemTemplate> </Bortable>
_backlog: Write the spec, Review the PR, Ship it, Celebrate
@bind-Items changes it in placeCapabilities
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.
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.
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
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.
<Bortable @bind-Items="_todo" TItem="Card" Group="board">…</Bortable> <Bortable @bind-Items="_done" TItem="Card" Group="board">…</Bortable>
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.
<Bortable Items="_widgets" TItem="Widget" Group="form" Pull="BortablePull.Clone" Sort="false" Put="false">…</Bortable>
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.
<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.
| Concern | SortableJS wrapper | Bortable |
|---|---|---|
| Who moves the items | JavaScript, in the DOM | C#, in your list - Blazor renders the result |
| Bound data | Patched after the fact from DOM indexes | @bind-Items, changed in place |
| Typed items | Indexes and element ids | Bortable<TItem> and typed event arguments |
| Script to ship | The library plus interop glue | A small module, imported on demand |
| Hosting models | Usually WebAssembly-first | WebAssembly, 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.
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