Sortable

MokaSortable<TItem> provides drag-and-drop list reordering powered by a lightweight JS module. It works with any IList<TItem> and automatically mutates the list in-place on a successful drop. An OnReorder callback fires with old and new indices for external state synchronisation.

Multiple MokaSortable instances can participate in cross-list dragging by sharing the same Group name.

Parameters

Name Type Default Description
Items IList<TItem> required The mutable list to render and reorder
ItemTemplate RenderFragment<TItem>? Custom item renderer
OnReorder EventCallback<(int OldIndex, int NewIndex)> Fired after a successful reorder
DragHandle bool false Only the grip handle initiates drag
Horizontal bool false Horizontal flex layout
Group string? Cross-list group name
IsItemDisabled Func<TItem, bool>? Per-item disabled predicate
ItemKey Func<TItem, object>? Key selector for stable rendering
Class string? Additional CSS classes on the container
Style string? Additional inline styles

Basic Vertical List

@code {
    List<string> _items = ["Alpha", "Beta", "Gamma", "Delta", "Epsilon"];
}

<MokaSortable Items="_items">
    <ItemTemplate Context="item">
        <div class="moka-sortable-item">@item</div>
    </ItemTemplate>
</MokaSortable>

Horizontal Orientation

@code {
    List<string> _columns = ["Name", "Status", "Priority", "Assignee"];
}

<MokaSortable Items="_columns" Horizontal>
    <ItemTemplate Context="col">
        <div style="padding:8px 16px;background:var(--moka-color-surface-2);border-radius:4px;cursor:grab">
            @col
        </div>
    </ItemTemplate>
</MokaSortable>

Drag Handle

When DragHandle is true only the .moka-sortable-handle element initiates drag. The rest of the item receives normal pointer events.

@code {
    record Task(string Id, string Title);
    List<Task> _tasks = [
        new("1", "Design system audit"),
        new("2", "Update dependencies"),
        new("3", "Write release notes"),
        new("4", "Deploy to staging"),
    ];
}

<MokaSortable Items="_tasks" DragHandle ItemKey="t => t.Id">
    <ItemTemplate Context="task">
        <div style="display:flex;align-items:center;gap:8px;padding:8px;
                    border:1px solid var(--moka-border-color);border-radius:4px">
            <span class="moka-sortable-handle" style="cursor:grab;color:var(--moka-color-text-secondary)">
                <MokaIcon Icon="MokaIcons.Content.Sort" Size="MokaSize.Sm" />
            </span>
            @task.Title
        </div>
    </ItemTemplate>
</MokaSortable>

OnReorder Callback

The list is mutated in place before OnReorder fires. Use the callback to persist the new order.

@code {
    List<string> _items = ["First", "Second", "Third", "Fourth"];
    string _log = "";
}

<MokaSortable Items="_items">
    <ItemTemplate Context="item">
        <div style="padding:8px 12px;border:1px solid var(--moka-border-color);border-radius:4px">
            @item
        </div>
    </ItemTemplate>
</MokaSortable>
@if (!string.IsNullOrEmpty(_log))
{
    <MokaCaption>@_log</MokaCaption>
}

Disabled Items

Items for which IsItemDisabled returns true render with a moka-sortable-item--disabled class and cannot be dragged.

@code {
    record Priority(string Name, bool Locked);
    List<Priority> _list = [
        new("Critical", true),
        new("High", false),
        new("Medium", false),
        new("Low", false),
        new("Backlog", true),
    ];
}

<MokaSortable Items="_list" IsItemDisabled="p => p.Locked" ItemKey="p => p.Name">
    <ItemTemplate Context="p">
        <div style="display:flex;align-items:center;gap:8px;padding:8px 12px;
                    border:1px solid var(--moka-border-color);border-radius:4px">
            @if (p.Locked)
            {
                <MokaIcon Icon="MokaIcons.Toggle.Lock" Size="MokaSize.Sm" Color="MokaColor.Secondary" />
            }
            @p.Name
        </div>
    </ItemTemplate>
</MokaSortable>

Cross-List Drag

Two or more MokaSortable instances sharing the same Group allow items to be dragged between them.

@code {
    List<string> _todo = ["Design", "Development", "Testing"];
    List<string> _done = ["Planning", "Research"];
}

<div style="display:flex;gap:24px">
    <div>
        <MokaLabel>To Do</MokaLabel>
        <MokaSortable Items="_todo" Group="kanban">
            <ItemTemplate Context="item">
                <div style="padding:8px 12px;background:var(--moka-color-surface-2);border-radius:4px">
                    @item
                </div>
            </ItemTemplate>
        </MokaSortable>
    </div>
    <div>
        <MokaLabel>Done</MokaLabel>
        <MokaSortable Items="_done" Group="kanban">
            <ItemTemplate Context="item">
                <div style="padding:8px 12px;background:var(--moka-color-surface-2);border-radius:4px">
                    @item
                </div>
            </ItemTemplate>
        </MokaSortable>
    </div>
</div>
Last updated: 2026-04-11
Was this page helpful?