Skip to content

Swimlanes

Swimlanes split one workflow into groups. For example, Product and Platform each have To do, In progress and Done cards.

Interactive example

Separate work by team

The same workflow is divided into Product and Platform lanes.

Try it: Move the research card to the Platform lane.

Loading example…

What to notice: The card’s team changes while its workflow status remains To do.

TypeScript source

Install the packages from Getting Started, then use this source in your app.

board-actions.ts
import type { KanbanPlugin, KanbanConfig } from '@revolist/kanban';
import type { WorkCard } from './first-board';
export function moveResearch(kanban: KanbanPlugin<WorkCard>, columnProp: string) {
return kanban.moveCards(['task-1'], { columnProp });
}
export function moveResearchToPlatform(kanban: KanbanPlugin<WorkCard>) {
return kanban.moveCards(['task-1'], { columnProp: 'todo', swimlaneId: 'platform' });
}
export function selectTodoCards(kanban: KanbanPlugin<WorkCard>) {
kanban.setSelectedCardIds(kanban.getCards().filter(card => card.status === 'todo').map(card => card.id));
}
export const workInProgress: Partial<KanbanConfig<WorkCard>> = {
columns: [{ prop: 'todo', name: 'To do' }, { prop: 'doing', name: 'In progress', wipLimit: 1 }, { prop: 'done', name: 'Done' }],
wipBehavior: 'block',
};
export const startBeforeDone: Partial<KanbanConfig<WorkCard>> = {
rules: { canDrop: ({ cards, targetColumn }) => targetColumn.prop === 'done' && cards.some(card => card.status === 'todo') ? 'Start the task before marking it done.' : true },
};
export function frenchBoard(config: KanbanConfig<WorkCard>): KanbanConfig<WorkCard> {
return { ...config, locale: 'fr-FR', columns: config.columns?.map(column => ({ ...column, name: ({ todo: 'À faire', doing: 'En cours', done: 'Terminé' } as Record<string, string>)[String(column.prop)] ?? column.name })), labels: { ...config.labels, emptyColumn: 'Aucune carte', dropCardHere: 'Déposer une carte ici', moveCancelled: 'Déplacement annulé', editCard: 'Modifier la carte', deleteCard: 'Supprimer la carte' } };
}
Download board-actions.ts

Try it: collapse a team, expand it again, then move a card to another team. The card remains in the same source collection; its lane field changes.

Start with Your First Board. The snippets below extend that grid; config denotes your existing Kanban configuration.

Swimlane labels render in a dedicated row-header column by default. Use swimlaneLayout: 'top' to render a compact header across the workflow columns before each lane instead. Both layouts keep counts, custom header content, and per-lane collapse controls.

grid.kanban = {
columns: config.columns,
swimlaneField: 'team',
swimlaneLayout: 'top', // use 'column' for the dedicated label column
swimlanes: [
{
id: 'product',
title: 'Product team',
collapsible: true,
swimlaneTemplate: (h, context) => h('div', { class: 'product-lane' }, [
h('strong', undefined, context.swimlane.title),
h('span', null, `${context.visibleCount} / ${context.totalCount}`),
h('button', { type: 'button', onClick: context.toggleCollapsed }, 'Toggle'),
]),
},
{ id: 'platform', title: 'Platform team', collapsible: true },
],
};

column is the default. swimlaneColumn width and collapse settings apply only to that dedicated-column layout. A lane’s swimlaneTemplate fully controls its header content in either layout while Kanban keeps the outer lane row and identity semantics.

Setting only swimlaneField derives lanes in first-appearance order. Provide definitions when labels, order, heights, collapse controls, styles, or lane-specific WIP limits matter.

grid.kanban = {
columns: config.columns,
swimlaneField: 'team',
swimlanes: [
{ id: 'product', title: 'Product team', wipLimits: { doing: 2 } },
{ id: 'platform', title: 'Platform team', collapsed: false },
],
};

Set swimlane order through the configuration array. Swimlanes cannot be drag-reordered.

For a board that does not use swimlanes, disable the row-header presentation directly in Kanban configuration:

grid.kanban = {
columns: config.columns,
swimlaneColumn: false,
};

Cards still use the internal implicit lane for ordering and virtualization, but no swimlane label or lane-collapse control is rendered.

The swimlane row-header column can collapse independently from the lanes. In its compact state it shrinks to a narrow rail and renders lane titles as vertical badges; cards and lane heights are unchanged.

grid.kanban = {
columns: config.columns,
swimlaneField: 'team',
swimlaneColumn: {
title: 'Teams',
width: 176,
collapsedWidth: 52,
collapsible: true,
collapsed: false,
},
};

Use getSwimlaneColumnCollapsed() and setSwimlaneColumnCollapsed(...) on the plugin instance when an application toolbar also needs to control the state. This is separate from each swimlane definition’s collapsed state, which reduces that lane’s projected row height.

Next, limit work in progress or customize lane headers. See the Kanban API for exact types and signatures.