Skip to content

Move Rules

Use rules when a move needs a business decision: Done work cannot move backward, locked cards cannot be dragged, or approval is required before completion.

Interactive example

Require work to start before completion

A task cannot move directly from To do to Done.

Try it: Try Complete research, then Start research and complete it again.

Loading example…

What to notice: The rule rejects the first move and accepts the valid workflow.

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: choose Complete research before starting it: the rule blocks the move. Choose Start research, then Complete research again. Local rules help users understand the workflow; your backend still enforces authorization when saving.

Use allowedFrom for simple workflow transitions and rules for application policy. This extends the grid and config from Your First Board:

grid.kanban = {
...config,
rules: {
canDrop: ({ cards, targetColumn }) =>
targetColumn.prop === 'done' && cards.some(card => card.status === 'todo')
? 'Start the task before marking it done.'
: true,
},
};

canDrag and canDrop accept false or a localized reason string to reject an action. canSelect and canEdit return booleans. These hooks receive your application cards, so you can base decisions on fields such as a lock flag or permissions.

cardRules is an ordered declarative layer for badges, classes, styles, and permissions. Matching classes and badges accumulate, later styles override earlier properties, and any explicit permission denial wins.

grid.kanban = {
...config,
cardRules: [{
id: 'high-priority',
when: ({ card }) => card.priority === 'high',
result: {
className: 'card--high-priority',
badges: [{ label: 'High priority' }],
style: { '--kanban-card-accent': '#dc2626' },
},
}],
};

rules and beforekanbancardmove decide synchronously. For the optional complete-dataset managed adapter, approveMove can await approval. Approval is not a save, and it does not replace the final backend permission check.

See WIP limits, card design and application saving. See the Kanban API for exact types and signatures.