Group-by switch + reorderable lanes
Board structure, live: switch the lane axis at runtime (Status / Assignee / Priority) and the board re-buckets, and drag a lane header to reorder the columns. Both built-in - the group-by switch resets card positions to the new field; lane order is tracked and persists with persistKey. Board / Table switcher. (requires @svgrid/enterprise)
A live, editable Svelte 5 data grid example from the SvGrid gallery (Kanban). See the SvGrid documentation for the full API.
About this example
Live board structure on the Svelte 5 data grid: switch the lane axis at runtime between Status, Assignee and Priority by changing board.groupBy and the board re-buckets, and drag a lane header to reorder the columns with reorderableLanes. Both are built in: the group-by switch resets card positions to the new field, lane order is reported through onLaneReorder and persists with persistKey, and a Board and Table switcher shows it is one grid.
Change the lane axis at runtime (Status / Assignee / Priority) and the board re-buckets live; drag a lane header to reorder the columns. Both are built-in - the group-by switch resets card positions to the new field, and lane order is tracked (and persists with persistKey).
Imports, features and API used
Imports: @svgrid/enterprise, @svgrid/grid
Table features registered: rowSortingFeature, columnFilteringFeature
Columns: title (Task), assignee (Assignee), priority (Priority), status (Status)
Frequently asked questions
What happens when groupBy changes?
The board rebuilds its lanes from the distinct values of the new field, or the lanes array if given, and places each card by its value of that field; nothing in the data changes.
How is lane order saved?
board.persistKey names a localStorage key the board writes its layout to, including lane order and collapsed lanes, and restores on load; onLaneReorder also reports the new order so you can store it elsewhere.
Can lanes be renamed?
Yes. With onLaneRename provided, a lane header becomes editable and the new label is reported to your handler.
Related documentation
Related articles
- Building a Kanban Board in Svelte 5 (Without a Board Library) - How to turn a Svelte data grid into a drag-and-drop Kanban board with one prop - lanes, WIP limits, swimlanes, keyboard moves, and virtualized lanes for large boards.
Source code (350-kanban-structure.svelte)
<script lang="ts">
// Kanban board rendering is an Enterprise feature - register the renderer.
import { enableBoardView } from "@svgrid/enterprise";
enableBoardView();
/**
* 350. Board structure: group-by switch + reorderable lanes
* ---------------------------------------------------------
* Change the lane axis at runtime (Status / Assignee / Priority) and the
* board re-buckets live; drag a lane header to reorder the columns. Both are
* built-in - the group-by switch resets card positions to the new field, and
* lane order is tracked (and persists with `persistKey`).
*/
import { SvGrid, tableFeatures, rowSortingFeature, columnFilteringFeature, type ColumnDef, type BoardCardMoveEvent } from '@svgrid/grid'
type Task = { id: number; title: string; status: string; assignee: string; priority: string }
const features = tableFeatures({ rowSortingFeature, columnFilteringFeature })
let view = $state<'board' | 'table'>('board')
let groupField = $state<'status' | 'assignee' | 'priority'>('status')
let rows = $state<Task[]>([
{ id: 1, title: 'Auth refresh tokens', status: 'todo', assignee: 'Sam Rivera', priority: 'High' },
{ id: 2, title: 'Board DnD', status: 'in_progress', assignee: 'Lee Park', priority: 'High' },
{ id: 3, title: 'Default card', status: 'in_progress', assignee: 'Sam Rivera', priority: 'Medium' },
{ id: 4, title: 'WIP styling', status: 'review', assignee: 'Ada Cole', priority: 'Low' },
{ id: 5, title: 'Docs pass', status: 'todo', assignee: 'Lee Park', priority: 'Medium' },
{ id: 6, title: 'Release 1.4', status: 'done', assignee: 'Ada Cole', priority: 'High' },
{ id: 7, title: 'Keyboard a11y', status: 'todo', assignee: 'Sam Rivera', priority: 'High' },
])
const columns: ColumnDef<any, Task>[] = [
{ field: 'title', header: 'Task', editorType: 'text' },
{ field: 'assignee', header: 'Assignee', editorType: 'text' },
{ field: 'priority', header: 'Priority', editorType: 'text' },
{ field: 'status', header: 'Status', editorType: 'text' },
]
const statusLanes = [
{ id: 'todo', title: 'To do' },
{ id: 'in_progress', title: 'In progress', color: '#f59e0b' },
{ id: 'review', title: 'Review', color: '#8b5cf6' },
{ id: 'done', title: 'Done', color: '#22c55e' },
]
function onCardMove(e: BoardCardMoveEvent<Task>) {
;(e.row as Record<string, unknown>)[groupField] = e.toLane
}
const GROUPS: Array<typeof groupField> = ['status', 'assignee', 'priority']
</script>
<section class="flex flex-col flex-1 min-h-0 gap-3">
<div class="flex items-center justify-between gap-3 shrink-0 flex-wrap">
<div class="kb-note text-sm">
The same <strong><SvGrid></strong>, two views. Switch the lane axis live, and
<strong>drag a lane header</strong> to reorder columns.
</div>
<div class="flex items-center gap-3 shrink-0">
{#if view === 'board'}
<label class="flex items-center gap-1.5 text-sm">
Group by:
<select
class="kb-select px-2 py-1 rounded border bg-transparent capitalize"
bind:value={groupField}>
{#each GROUPS as g (g)}<option value={g}>{g}</option>{/each}
</select>
</label>
{/if}
<div class="kb-switch inline-flex rounded-md border overflow-hidden text-sm">
<button class="kb-seg px-3 py-1 {view === 'board' ? 'kb-seg-on' : ''}"
onclick={() => (view = 'board')}>Board</button>
<button class="kb-seg px-3 py-1 {view === 'table' ? 'kb-seg-on' : ''}"
onclick={() => (view = 'table')}>Table</button>
</div>
</div>
</div>
<div class="flex-1 min-h-0">
{#if view === 'board'}
<SvGrid responsive={true}
columnResize
data={rows}
columns={columns}
getRowId={(r) => String(r.id)}
containerHeight="100%"
board={{
groupBy: groupField,
lanes: groupField === 'status' ? statusLanes : undefined,
reorderableLanes: true,
editable: true,
laneSummary: (list) => `${list.length}`,
onCardMove,
}}
/>
{:else}
<SvGrid responsive={true}
columnResize
data={rows}
columns={columns}
features={features}
getRowId={(r) => String(r.id)}
sortable
filterable
enableInlineEditing
rowHeight={36}
containerHeight="100%"
fitColumns
/>
{/if}
</div>
<footer class="kb-foot text-sm shrink-0">
Grouped by <strong class="capitalize">{groupField}</strong> · {rows.length} tasks · drag a header to reorder.
</footer>
</section>
<style>
.kb-note { color: var(--sg-muted, #475569); }
.kb-foot { color: var(--sg-muted, #64748b); }
.kb-switch { border-color: var(--sg-border, #cbd5e1); }
.kb-select {
border-color: var(--sg-border, #cbd5e1);
color: var(--sg-fg, #0f172a);
}
.kb-seg {
background: transparent;
color: var(--sg-fg, #0f172a);
cursor: pointer;
}
.kb-seg-on {
background: var(--sg-accent, #1e293b);
color: var(--sg-on-accent, #fff);
}
</style>More Kanban examples
- Kanban board mode - The same <SvGrid>, same data + columns, rendered as a Kanban board by setting one `board` prop: rows become cards in horizontal lanes bucketed by a `status` field. Built-in drag-and-drop AND keyboard move (Space + arrows) reassign the lane and reorder; double-click / F2 opens a built-in card editor; WIP limits, add-card, and a rich card snippet (assignee avatar + priority chip). Toggle Board / Table to see it is one grid.
- Sprint board (swimlanes + WIP) - An agile sprint board: status lanes crossed with a swimlane per assignee, an enforced WIP limit on In progress, and a story-point roll-up in every lane header via board.laneSummary. Drag across a band to reassign the owner; double-click to edit. Collapsible lanes.
- Sales pipeline (deal board) - A CRM deal board: stages as lanes with a live total-value roll-up per stage (board.laneSummary). Rich deal cards; drag to advance a deal, double-click to edit. Moves and edits persist to localStorage via onCardMove / onCardCommit, so a reload restores the board.
- Large board (virtualized) - Thousands of cards per lane kept smooth with board.virtualized - only the cards in view are in the DOM. A slider scales the board from 1k to 50k cards; drag-and-drop and keyboard move still work across the whole window.
- Support triage board - A helpdesk triage board: status lanes crossed with a priority swimlane, a search box front-and-center, and an SLA colour bar on each card driven by ticket age. Drag across a band to re-triage a ticket priority.