Swimlanes
Swimlanes split one workflow into groups. For example, Product and Platform each have To do, In progress and Done cards.
Separate work by team
The same workflow is divided into Product and Platform lanes.
Try it: Move the research card to the Platform lane.
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.
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' } };}import { defineCustomElements } from '@revolist/revogrid/loader';import { KanbanPlugin, type KanbanConfig } from '@revolist/kanban';import '@revolist/revogrid-pro/styles.css';import '@revolist/kanban/styles.css';
export interface WorkCard { id: string; title: string; status: string; order: number; description?: string; team?: string; owner?: string; priority?: string; tags?: string[]; progress?: number; startDate?: string; endDate?: string;}export const demoCards: WorkCard[] = [ { id: 'task-1', title: 'Research customer needs', description: 'Collect feedback for the next release.', status: 'todo', team: 'product', owner: 'Ana', priority: 'normal', order: 1000 }, { id: 'task-2', title: 'Build onboarding', description: 'Make the first session useful.', status: 'doing', team: 'product', owner: 'Mia', priority: 'high', order: 1000 }, { id: 'task-3', title: 'Improve API errors', description: 'Return actionable validation messages.', status: 'todo', team: 'platform', owner: 'Rui', priority: 'normal', order: 2000 }, { id: 'task-4', title: 'Release checklist', description: 'Ready for the next deployment.', status: 'done', team: 'platform', owner: 'Sam', priority: 'normal', order: 1000 },];export const boardConfig: KanbanConfig<WorkCard> = { columns: [{ prop: 'todo', name: 'To do', size: 210 }, { prop: 'doing', name: 'In progress', size: 210 }, { prop: 'done', name: 'Done', size: 210 }], card: { titleField: 'title' }, cardRowHeight: 88, addCard: false, swimlaneColumn: false,};
export async function configureBoard( grid: HTMLRevoGridElement, config: KanbanConfig<WorkCard> = boardConfig, cards: readonly WorkCard[] = demoCards, extraPlugins: HTMLRevoGridElement['plugins'] = [],) { await defineCustomElements(); grid.plugins = [KanbanPlugin, ...extraPlugins]; // The packaged editor and history install automatically. grid.columns = [{ prop: 'title', name: 'Title' }, { prop: 'status', name: 'Status' }, { prop: 'owner', name: 'Owner' }]; // Cached remote data can hydrate immediately: finish initial grid setup first. grid.kanban = config.remote ? { ...config, remote: false } : config; grid.source = cards.map(card => ({ ...card })); await grid.componentOnReady(); const plugins = await grid.getPlugins(); const kanban = plugins.find((plugin): plugin is KanbanPlugin<WorkCard> => plugin instanceof KanbanPlugin); if (!kanban) throw new Error('Kanban could not start.'); if (config.remote) grid.kanban = config; return kanban;}
// Give your container a width and mount once after it exists in the DOM.export async function createKanbanBoard(container: HTMLElement) { await defineCustomElements(); const grid = document.createElement('revo-grid'); grid.style.cssText = 'display:block;width:100%;height:400px'; container.append(grid); try { const kanban = await configureBoard(grid); return { grid, kanban, destroy() { grid.remove(); } }; } catch (error) { grid.remove(); throw error; }}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 placement
Section titled “Swimlane placement”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.
Derived or explicit lanes
Section titled “Derived or explicit lanes”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.
Collapsible swimlane labels
Section titled “Collapsible swimlane labels”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 steps
Section titled “Next steps”Next, limit work in progress or customize lane headers. See the Kanban API for exact types and signatures.