Interaction
Drag & Drop
Cards, columns, and swimlanes can all be reordered by dragging. Cards can be moved between columns and swimlanes. A placeholder shows the drop target while dragging.
Card Drag
// Intercept card move (before mutation — cancelable)
board.events.cardMoving$.subscribe(args => {
// args: { card, fromColumnId, toColumnId, toIndex, toSwimlaneId, cancel }
// Prevent moving to a specific column
if (args.toColumnId === 'archived-column-id') {
args.cancel = true;
}
});
// React after card was moved by drag (after mutation)
board.events.cardMovedByDrag$.subscribe(args => {
// args: { card, fromColumnId, toColumnId, toIndex }
console.log('Card moved from', args.fromColumnId, 'to', args.toColumnId);
});Column Drag
// Intercept column reorder (cancelable)
board.events.columnMoving$.subscribe(args => {
// args: { column, fromIndex, toIndex, cancel }
args.cancel = true; // prevent reorder
});
// React after column was moved
board.events.columnMovedByDrag$.subscribe(args => {
// args: { column, fromIndex, toIndex }
});Swimlane Drag
// Intercept swimlane reorder (cancelable)
board.events.swimlaneMoving$.subscribe(args => {
// args: { swimlane, fromIndex, toIndex, cancel }
});
// React after swimlane was moved
board.events.swimlaneMovedByDrag$.subscribe(args => {
// args: { swimlane, fromIndex, toIndex }
});Selection
Click a card to select it. Hold Ctrl/Cmd to multi-select. The selected cards are available via the selectedCards property.
// Get selected cards
const selected = board.selectedCards;
const single = board.selectedCard; // last selected, or null
// Set selection programmatically
board.selectedCards = [card1, card2];
board.selectedCard = card1;
// Listen for selection changes
board.events.selectedCardsChanged$.subscribe(cards => {
console.log('Selected:', cards.map(c => c.title));
});
// Listen for hover changes
board.events.hoveredCardChanged$.subscribe(card => {
console.log('Hovered:', card?.title ?? 'none');
});Click Events
Click and double-click events are available for cards, columns, swimlanes, and empty column areas.
// Card click / double-click
board.events.cardClick$.subscribe(({ card, domEvent }) => {
console.log('Clicked:', card.title);
});
board.events.cardDblClick$.subscribe(({ card, domEvent }) => {
openCardEditor(card);
});
// Column header click / double-click
board.events.columnClick$.subscribe(({ column, domEvent }) => { /* ... */ });
board.events.columnDblClick$.subscribe(({ column, domEvent }) => { /* ... */ });
// Swimlane header click / double-click
board.events.swimlaneClick$.subscribe(({ swimlane, domEvent }) => { /* ... */ });
board.events.swimlaneDblClick$.subscribe(({ swimlane, domEvent }) => { /* ... */ });
// Empty column area click / double-click
board.events.columnEmptyClick$.subscribe(({ column, swimlane, domEvent }) => { /* ... */ });
board.events.columnEmptyDblClick$.subscribe(({ column, swimlane, domEvent }) => {
// Double-click to quickly add a card
const card = new KanbanCard(null, column.id);
card.title = 'New Card';
if (swimlane) card.swimlaneId = swimlane.id;
board.data.addCard(card);
});Keyboard Navigation
The board supports keyboard navigation and shortcuts. Use arrow keys to move between cards, Enter to select, and Delete to remove.
Filtering
Apply a filter function to show only cards matching certain criteria. Filtered-out cards are hidden from the board but remain in the data model.
// Show only high-priority cards
board.data.filter = (card) => {
return card.priority === 'high' || card.priority === 'critical';
};
// Filter by tag
board.data.filter = (card) => card.tags.includes('backend');
// Filter by assignee
board.data.filter = (card) => card.assigneeIds.includes('alice');
// Clear the filter
board.data.filter = null;Card Deletion
Intercept card deletion to show a confirmation dialog or prevent deletion of certain cards.
board.events.cardDeleting$.subscribe(args => {
// args: { card, cancel }
if (args.card.priority === 'critical') {
args.cancel = true; // prevent deletion of critical cards
}
});Undo & Redo
All operations (card moves, additions, deletions, property changes) are tracked and can be undone/redone.
board.undo();
board.redo();
// Check if undo/redo is available
board.canUndo$.subscribe(canUndo => {
undoButton.disabled = !canUndo;
});
board.canRedo$.subscribe(canRedo => {
redoButton.disabled = !canRedo;
});
// Clear undo history
board.clearUndoHistory();
// Batch multiple operations into a single undo step
board.beginBatch();
board.data.addCard(card1);
board.data.addCard(card2);
board.data.addCard(card3);
board.endBatch();
// Now a single undo() removes all three cards
// Listen for undo/redo events
board.events.undone$.subscribe(() => console.log('Undone'));
board.events.redone$.subscribe(() => console.log('Redone'));Copy & Paste
Copy, cut, and paste cards between columns and swimlanes.
// Copy selected cards (or pass specific cards)
board.copyCards();
board.copyCards([card1, card2]);
// Cut selected cards
board.cutCards();
// Paste into a target column (and optionally a swimlane)
board.pasteCards('target-column-id');
board.pasteCards('target-column-id', 'target-swimlane-id');