Visitar URL original
Bortable - drag-and-drop sortable lists for Blazor
Skip to content
Open source · MIT · net10.0 / net11.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, 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.

Rules →

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

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 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
An unhandled error has occurred. Reload 🗙