Skip to content

Cards, Columns, And Ordering

Kanban reads and updates ordinary RevoGrid source rows. A card needs a stable ID, workflow-column value, and preferably a numeric order:

grid.source = [
{
workId: 'PAY-104',
summary: 'Retry declined payments',
stage: 'active',
rank: 1000,
team: 'billing',
},
];
grid.kanban = {
idField: 'workId',
columnField: 'stage',
orderField: 'rank',
swimlaneField: 'team',
columns: [
{ prop: 'queued', name: 'Queued' },
{ prop: 'active', name: 'Active' },
{ prop: 'done', name: 'Done' },
],
};

When a card has no rank, source order is used. The first move assigns fractional ranks between neighboring cards. If the available numeric gap becomes unsafe, Kanban rebalances only the destination bucket and reports the affected IDs in kanbancardmove.

Use kanban.fields when the same application records also feed Scheduler or Gantt. The keys describe field meaning and the values name properties in your source rows:

grid.kanban = {
fields: {
id: 'id',
title: 'name',
status: 'workflowStatus',
start: 'startDate',
end: 'endDate',
color: 'color',
progress: 'percentDone',
resourceId: 'owner',
},
columns: [
{ prop: 'todo', name: 'To do' },
{ prop: 'doing', name: 'In progress' },
{ prop: 'done', name: 'Done' },
],
};

Kanban defaults these meanings to id, title, status, startDate, endDate, color, progress, and assignees. An explicitly mapped shared key takes precedence over the corresponding idField, columnField, or card.*Field option. The older options remain available for existing boards.

The mapping only selects properties. It does not convert dates or progress units. resourceId represents one resource identifier; Kanban also continues to accept the established array form through card.assigneeField. Fields that Kanban does not change, including application metadata, remain on the original card. Moves, editor changes, remote updates, and undo/redo preserve authored property names and do not add internal Kanban aliases to source rows.

Use kanban.remote when the canonical collection should arrive from one whole-board offset/limit stream:

const loadCards = async (skip: number, limit: number) => {
const response = await fetch(`/api/cards?skip=${skip}&limit=${limit}`);
const result = await response.json();
return {
data: result.cards,
total: result.total,
};
};
grid.kanban = {
columns,
remote: {
loadData: loadCards,
total: 100_000,
chunkSize: 100,
preloadThreshold: 0.75,
placeholder: (index) => ({
id: `remote-${index}`,
status: statuses[index % statuses.length],
order: Math.floor(index / statuses.length),
}),
},
};

With total and placeholder, Kanban works like Infinity Scroll: it creates the known number of lightweight positions, renders an animated skeleton for each unloaded card row, and replaces the requested 100-card slice in place. The placeholder only needs a unique temporary id, workflow column, swimlane when configured, and rank fields. Its ID may differ from the returned server card ID; the source offset owns replacement identity. The workflow, swimlane, and rank values must match the corresponding server card. Scrolling directly to an unloaded area requests that area’s chunk; approaching a loaded chunk boundary preloads the next one.

Without placeholder, remote mode retains append paging: page zero replaces the current cards and later pages append as the board nears its scroll boundary. Both modes preserve loaded cards on errors and expose the same inline retry action.

The result can be a card array or { data, total?, hasMore? }. In append mode, a returned total takes precedence over hasMore; otherwise an explicit hasMore wins, then the configured or previously returned total is consulted, and only then does a short page end the stream. Indexed mode treats the configured total as its fixed layout: every request must return its complete requested slice, and response total or hasMore values must agree with that layout. A mismatch keeps the slice retryable and emits the remote error event.

Local update and move operations preserve indexed server positions. Create and delete are rejected in indexed mode because they would shift every later offset; perform those operations on the server and call refreshRemote(). Replacing grid.source externally restarts indexed loading from page zero. In append mode, local CRUD does not rewind the cursor and an external source replacement adopts its length as the next offset. Both behaviors invalidate a late response.

The loader type and indexed chunk behavior are shared with InfinityScrollPlugin, so the same application adapter can power a table or Kanban view. Kanban needs the additional placeholder factory because an unloaded card’s workflow column and optional swimlane cannot be inferred from a global total. The two plugins should not own one grid simultaneously because Kanban projects cards into its own virtual rows.

kanban.columns is the workflow schema; grid.columns remains the canonical table/card-field schema. A card belongs to a workflow column when card[columnField] === column.prop.

Workflow definitions reuse the safe RevoGrid header and sizing subset and add Kanban behavior such as WIP limits, collapse state, transition rules, metadata, and card filtering.

The array is optional and defaults to []. This supports loading workflow metadata and cards independently: no columns plus no cards is an empty board, while cards without configured columns use the managed Unmapped column by default. Supplying workflow columns later rebuilds the projection from the same canonical grid.source records.

columns: [
{ prop: 'queued', name: 'Queued', size: 260 },
{
prop: 'active',
name: 'Active',
minSize: 240,
maxSize: 360,
wipLimit: 4,
allowedFrom: ['queued', 'active'],
collapsible: true,
},
]

Projected workflow columns keep the exact authored prop; no synthetic grid property is introduced. If name is omitted, Kanban displays String(prop). The default size is 288 and minSize is 220. The core column-move plugin handles header dragging; kanbancolumnorderchange exposes the resulting prop order for persistence.

Every workflow column is collapsible by default. Set collapsible: false to lock a column open, or use collapsed: true for its initial state. At runtime, setColumnCollapsed(prop, collapsed) changes one column and getCollapsedColumnProps() returns the active collapsed set. A collapsed column remains visible as a narrow rail with a vertical resolved-name badge, so users can restore it without losing board context.

  • Unknown statuses render in a localized Unmapped column by default. Set unmappedColumn: false only when the host deliberately hides them.
  • Missing or duplicate card IDs produce blocking diagnostics. Those cards remain visible but cannot be moved.
  • Duplicate column props produce a blocking configuration diagnostic.
  • Invalid numeric ranks fall back to source order and produce a diagnostic.

Listen to kanbandiagnostics or call getDiagnostics() on the plugin during development and after remote data refreshes.

Column and swimlane headers distinguish:

  • visible count: cards passing RevoGrid filters, Kanban search, and the optional predicate;
  • total count: all canonical cards in that bucket.

WIP limits always use total counts so a filter cannot accidentally permit over-capacity work.

In append mode, both counts describe the cards loaded so far. In indexed mode, known placeholder positions participate in total and visible counts, so loading a page does not change an unfiltered header count. Placeholders remain non-interactive, and active filters can exclude positions that cannot match. Enforce server-side rules in the application whenever Kanban does not have the authoritative records needed for the decision.