Skip to main content

Kanban Board

Overview​

The Kanban Board provides visual task management with a column-based workflow system. Teams organize work into customizable columns with WIP (Work In Progress) limits, track progress with cards, and collaborate through comments and activity tracking.

Kanban Board Kanban Board with drag-and-drop columns, cards, and team collaboration features

Key Features​

  • Column-Based Workflow - Drag-and-drop cards between columns
  • WIP Limits - Prevent bottlenecks with work-in-progress constraints
  • Card Properties - Priority, due dates, estimated hours, assignees
  • Collaboration - Comments, activity logs, read receipts
  • Assignee Management - Team member assignment with roles
  • Labels & Filtering - Organize cards with custom labels
  • Role-Based Visibility - SRM, SDM, Flow Manager roles
  • Real-time Updates - WebSocket support for live collaboration

Core Data Structures​

interface KanbanCard {
id: number;
title: string;
description?: string;
priority: 'low' | 'medium' | 'high';
status: string;
due_date?: string;
estimated_hours?: number;
assignees?: Assignee[];
labels?: KanbanLabel[];
comments?: Comment[];
activities?: Activity[];
created_at: string;
updated_at: string;
}

interface KanbanColumn {
id: number;
name: string;
wip_limit?: number;
position: number;
cards?: KanbanCard[];
card_count?: number;
}

interface Assignee {
id: number;
username?: string;
name?: string;
avatar_url?: string | null;
initials?: string;
color?: string;
kanban_role?: string | null;
kanban_role_short?: string | null;
kanban_role_color?: string | null;
}

interface KanbanLabel {
id: number;
name: string;
color: string;
}

Column Management​

Create Column​

Board → New Column → Name: "In Progress"
Set WIP Limit: 5 (optional)
Position: Auto-ordered

WIP Limits​

Work In Progress limits prevent bottlenecks:

// Check WIP before dropping card
const canDropCard = (column: KanbanColumn, newCard: KanbanCard) => {
if (!column.wip_limit) return true;
const currentCount = column.cards?.length || 0;
return currentCount < column.wip_limit;
};

// Visual feedback when limit exceeded
if (column.cards.length >= column.wip_limit) {
showWarning(`Column "${column.name}" has reached WIP limit`);
}

Card Management​

Card Properties​

interface CardForm {
title: string; // Required
description?: string; // Optional markdown
priority: 'low' | 'medium' | 'high';
due_date?: string; // ISO format
estimated_hours?: number;
assignees?: number[]; // User IDs
labels?: number[]; // Label IDs
}

Card Priority​

High - Urgent, blockers, critical work
Medium - Normal priority, should complete soon
Low - Nice-to-have, can defer if needed

Estimated Hours​

Track time estimates for capacity planning:

// Sum by assignee or column
const totalHours = cards.reduce((sum, card) =>
sum + (card.estimated_hours || 0), 0
);

const assigneeLoad = cards
.filter(c => c.assignees.some(a => a.id === userId))
.reduce((sum, c) => sum + (c.estimated_hours || 0), 0);

Assignee Management​

Team Member Assignment​

interface TeamMember {
id: number;
username: string;
email: string;
avatar_url?: string | null;
initials: string;
color: string;
kanban_role?: string | null;
kanban_role_short?: string | null;
}

Kanban Roles​

SRM - Scrum Master/Resource Manager
SDM - Software Development Manager
Flow Manager - Process improvement

Assign to Card​

Card → Assignees → Select Team Members
Multiple assignees supported
View assignee avatar/initials on card

Card Workflow​

Drag and Drop​

Using dnd-kit library:

import { DndContext, DragEndEvent } from '@dnd-kit/core';

const handleDragEnd = async (event: DragEndEvent) => {
const { active, over } = event;

if (!over) return;

const cardId = active.id;
const targetColumnId = over.id;

// Update card status
await updateCard(cardId, {
status: targetColumnId
});
};

Card Movement History​

Activity log tracks card movements:

{
type: 'card_moved',
from_column: 'backlog',
to_column: 'in_progress',
actor: 'john.doe',
timestamp: '2026-04-13T10:30:00Z'
}

Comments & Collaboration​

Comment Thread​

interface Comment {
id: number;
card_id: number;
author: User;
body: string;
attachments?: Attachment[];
created_at: string;
updated_at: string;
replies?: Comment[];
}

// Add comment
const handleAddComment = async (body: string, attachments?: File[]) => {
const formData = new FormData();
formData.append('body', body);
attachments?.forEach((file, i) => {
formData.append(`attachments[${i}]`, file);
});

await fetch(`/api/cards/${cardId}/comments`, {
method: 'POST',
body: formData,
headers: { 'Authorization': `Bearer ${token}` }
});
};

Activity Log​

Tracks all card activities:

interface Activity {
id: number;
card_id: number;
type: 'created' | 'updated' | 'commented' | 'moved' | 'assigned';
actor: User;
description: string;
changes?: Record<string, unknown>;
created_at: string;
}

Labels​

Label Management​

// Create label
const label = {
name: 'Feature',
color: '#3b82f6' // Blue
};

// Assign to card
card.labels = [label];

// Filter by labels
const filteredCards = cards.filter(c =>
c.labels.some(l => l.id === selectedLabelId)
);

Filtering & Sorting​

Filter Options​

// By assignee
cards.filter(c => c.assignees.some(a => a.id === userId));

// By priority
cards.filter(c => c.priority === 'high');

// By label
cards.filter(c => c.labels.some(l => l.name === 'bug'));

// By due date (overdue)
cards.filter(c => new Date(c.due_date) < new Date());

// By column
const columnCards = column.cards || [];

Sort Options​

// Sort by priority
cards.sort((a, b) =>
priorityOrder[a.priority] - priorityOrder[b.priority]
);

// Sort by due date
cards.sort((a, b) =>
new Date(a.due_date) - new Date(b.due_date)
);

// Sort by estimated hours
cards.sort((a, b) =>
(b.estimated_hours || 0) - (a.estimated_hours || 0)
);

Real-time Updates​

WebSocket Subscription​

// Subscribe to board updates
const channel = getEcho().channel(`kanban-board.${boardId}`);

channel.listen('CardMoved', (data) => {
updateCardPosition(data.card_id, data.new_column_id);
});

channel.listen('CardUpdated', (data) => {
updateCard(data.card_id, data.changes);
});

channel.listen('CardCreated', (data) => {
addCard(data.card);
});

API Endpoints​

# Board operations
GET /api/kanban-boards/{id} # Get board
PUT /api/kanban-boards/{id} # Update board

# Columns
POST /api/kanban-columns # Create column
PUT /api/kanban-columns/{id} # Update column
DELETE /api/kanban-columns/{id} # Delete column

# Cards
POST /api/kanban-cards # Create card
GET /api/kanban-cards/{id} # Get card
PUT /api/kanban-cards/{id} # Update card
DELETE /api/kanban-cards/{id} # Delete card
POST /api/kanban-cards/{id}/move # Move card

# Comments
POST /api/kanban-cards/{id}/comments # Add comment
DELETE /api/comments/{id} # Delete comment

# Assignees
POST /api/kanban-cards/{id}/assignees # Add assignee
DELETE /api/assignees/{id} # Remove assignee

Best Practices​

  1. Limit Columns - 5-7 columns for clarity
  2. Set Realistic WIP Limits - Based on team capacity
  3. Update Regularly - Keep cards current
  4. Use Clear Titles - Descriptive card names
  5. Assign Ownership - Clear responsibility
  6. Review Activities - Track what changed and why
  7. Archive Completed - Clean up finished cards
  8. Communicate Changes - Use comments for discussion

Workflow Examples​

Software Development​

Columns: Backlog → Design → In Progress → Review → Testing → Done
WIP Limits: 1 → 2 → 3 → 2 → 2 → ∞

Content Production​

Columns: Ideas → Outline → Draft → Review → Final → Published
WIP Limits: ∞ → 3 → 3 → 2 → 2 → ∞

Customer Support​

Columns: New → Assigned → In Progress → Waiting for Info → Resolved → Closed
WIP Limits: ∞ → 5 → 10 → 3 → ∞ → ∞

Troubleshooting​

Cards Not Updating​

  • Check WebSocket connection
  • Verify permission to edit card
  • Refresh board if stale

WIP Limit Not Enforcing​

  • Verify limit is set > 0
  • Check if user has override permission
  • Review column configuration

Comments Not Showing​

  • Ensure attachments are enabled (patron feature)
  • Check comment permissions
  • Verify card is not archived

Next Steps​