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, right-to-left rows and nested trees whose items move between branches and depths.
Nested sortables →Keyboard and screen readers
Pick an item up with Space, move it with the arrows - across lists too - and drop it; every step is announced, in words you can translate, and buttons can move items without a drag.
Keyboard →Rules and rollback
Refuse a position as it is reached (CanDrop), cap a list (MaxItems),
and let a server have the last word on a drop (ConfirmDrop) - refused, it slides back.
Touch, pen and mouse
A finger or a pen drags with pointer events - a long press picks an item up, a swipe still scrolls - and a drag near an edge scrolls the list or the page.
Touch and pen →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 what has to happen where the pointer is: it follows the drag, measures the pointer against the items for the swap thresholds, plays the FLIP animations, and calls .NET only when the drop position changes - so a drag stays smooth on Blazor Server too.
Which render modes does it work in?
Any interactive one: WebAssembly, Server, Auto and Hybrid, on .NET 10 and 11. 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?
Yes. A mouse drags with the browser's own HTML5 drag and drop; a finger or a pen drags with pointer events, so touch works in every mobile browser and web view, whether or not it supports native drag. A long press picks an item up, so a swipe over the list still scrolls the page.
Can it be used without a mouse?
Yes. Every list can be sorted from the keyboard - Space picks an item up, the arrows move it,
Space drops it, Escape puts it back - and each step is announced to screen readers.
MoveAsync and MoveToAsync move an item from code, for "move up"
buttons and item menus.
Can my code refuse a drop?
Yes: CanDrop vets every position before it is shown, MaxItems caps a
list, and ConfirmDrop asks your code - a server, a dialog - once the item is
dropped, putting everything back if it says no.
Put your lists in order
One package, one service, one stylesheet - and any list becomes sortable.
dotnet add package Bit.Bortable