Moving Cards
Move a card to show that its work has progressed. Reordering changes its position; moving across columns changes its workflow status.
Move a card
Drag cards, use the keyboard, or call the public move API.
Try it: Move research into In progress, then return it to To do.
What to notice: The app receives a committed move event with the changed cards.
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: drag Research customer needs into In progress. Or focus a card, press Space to pick it up, choose a destination with arrows and complete the move; Escape cancels. Use the live result message to confirm the changed card.
Card movement
Section titled “Card movement”Cards can be reordered within a workflow column or moved across columns and swimlanes. The complete non-interactive card surface starts a pointer drag and the card itself is the keyboard pickup target. Pointer capture keeps the drag active outside a card, and drop indicators plus localized announcements expose the proposed target.
During a drag, Kanban auto-scrolls the board horizontally and RevoGrid vertically. Filtered cards remain in the canonical bucket; visible cards act as insertion anchors so hidden records keep deterministic ranks.
Touch and mobile behavior
Section titled “Touch and mobile behavior”Kanban uses Pointer Events for mouse, pen, and touch rather than browser-native HTML drag-and-drop. A tap selects the card without creating a drag preview. Once touch movement crosses the drag threshold, pointer capture keeps the operation active and horizontal edge auto-scroll exposes off-screen workflow columns. Browser pointercancel events remove the preview and drop feedback without committing a move.
Draggable cards use touch-action: pan-y, so a vertical swipe can continue to scroll a long board from the card surface. A primarily horizontal gesture starts cross-column movement. Because mobile browsers arbitrate vertical gestures before application code, use the context menu, keyboard, or moveCards() API when an exact vertical reorder gesture is claimed by native scrolling.
Keep the grid sized to its container on phones; workflow columns retain their configured widths and scroll inside RevoGrid rather than compressing card content. Validate production integrations on physical iOS Safari and Android Chrome in addition to automated mobile emulation.
Empty alignment cells are not drop zones. A column exposes targets on its existing cards and once in the first empty cell immediately after its final visible card, preventing repeated empty placeholders across the swimlane.
Keyboard movement
Section titled “Keyboard movement”Focus the card, then:
| Key | Action |
|---|---|
| Space | Pick up or drop the selected card group. |
| Enter | Drop an active keyboard move; when no move is active, open the card editor. |
| Arrow keys | Move the proposed target through positions, columns, and lanes. |
| Escape | Cancel the active keyboard drag. |
The live region announces pickup, target changes, validation failures, cancelation, and completion. Application card content remains inside the managed shell so custom templates cannot remove these interactions.
Programmatic movement
Section titled “Programmatic movement”Locate the plugin through RevoGrid’s plugin collection and call moveCards:
import { KanbanPlugin } from '@revolist/kanban';
const plugins = await grid.getPlugins();const kanban = plugins.find((plugin): plugin is KanbanPlugin => plugin instanceof KanbanPlugin)!;
kanban.moveCards( ['task-1'], { columnProp: 'doing', index: 0 }, { reason: 'api' },);Programmatic moves use the same validation, events, provider update, ranking, and history path as pointer and keyboard moves. For asynchronous approval or persistence, call await kanban.moveCardsAsync(cardIds, target) to wait for server acceptance.
Save the result
Section titled “Save the result”The final kanbancardmove event contains the canonical cards changed by the move, including any rank-rebalanced neighbors. A final event reports what happened; cancel it before the move through rules or beforekanbancardmove, not afterward. See application-owned saving.
Next steps
Section titled “Next steps”Continue with selecting several cards, undo and redo or move rules. See the Kanban API for exact types and signatures.