Kanban Board Generator
Create customizable kanban boards with drag-and-drop functionality for project management and task tracking
Board Style
Card Style
Shadow
Column Width
Column Gap
Card Gap
Features
Card Content
Colors
Themes
Preview
To Do
3Design homepage wireframes
Write API documentation
Set up CI/CD pipeline
In Progress
2Implement user authentication
Create dashboard components
In Review
1Database schema design
Done
2Project setup and configuration
Design system documentation
HTML
<div class="kanban-board">
<div class="kanban-column">
<div class="column-header">
<h3 class="column-title">To Do</h3>
<span class="card-count">3</span>
</div>
<div class="column-content">
<div class="kanban-card" draggable="true">
<div class="card-labels">
<span class="card-label" style="background: #61bd4f">Design</span>
<span class="card-label" style="background: #eb5a46">High Priority</span>
</div>
<h4 class="card-title">Design homepage wireframes</h4>
<div class="card-footer">
<span class="card-due-date">Jan 15</span>
<span class="card-assignee">JD</span>
</div>
</div>
<!-- More cards... -->
</div>
<button class="add-card-btn">+ Add a card</button>
</div>
<!-- More columns... -->
</div>CSS
.kanban-board {
display: flex;
gap: 16px;
padding: 20px;
background: #f4f5f7;
min-height: 100vh;
overflow-x: auto;
}
.kanban-column {
flex: 0 0 280px;
background: #ebecf0;
border-radius: 8px;
display: flex;
flex-direction: column;
max-height: calc(100vh - 40px);
}
.column-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 16px;
}
.column-title {
margin: 0;
font-size: 14px;
font-weight: 600;
color: #5e6c84;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.card-count {
background: #dfe1e6;
color: #5e6c84;
font-size: 12px;
font-weight: 600;
padding: 2px 8px;
border-radius: 10px;
}
.column-content {
flex: 1;
padding: 0 8px 8px;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 12px;
}
.kanban-card {
background: #ffffff;
border-radius: 6px;
padding: 12px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
cursor: grab;
transition: transform 0.1s ease, box-shadow 0.1s ease;
}
.kanban-card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
}
.kanban-card.dragging {
opacity: 0.5;
cursor: grabbing;
}
.card-labels {
display: flex;
flex-wrap: wrap;
gap: 4px;
margin-bottom: 8px;
}
.card-label {
display: inline-block;
height: 8px;
min-width: 32px;
border-radius: 4px;
}
.card-title {
margin: 0 0 8px;
font-size: 14px;
font-weight: 500;
color: #172b4d;
line-height: 1.4;
}
.card-footer {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 8px;
font-size: 12px;
color: #5e6c84;
}
.card-due-date {
display: flex;
align-items: center;
gap: 4px;
}
.card-due-date::before {
content: '📅';
font-size: 12px;
}
.card-assignee {
width: 28px;
height: 28px;
border-radius: 50%;
background: #0079bf;
color: white;
display: flex;
align-items: center;
justify-content: center;
font-size: 11px;
font-weight: 600;
}
.add-card-btn {
width: 100%;
padding: 10px;
margin: 8px;
margin-top: auto;
background: transparent;
border: none;
border-radius: 6px;
color: #5e6c84;
font-size: 14px;
cursor: pointer;
text-align: left;
transition: background 0.2s;
}
.add-card-btn:hover {
background: rgba(0,0,0,0.05);
color: #172b4d;
}JavaScript
class KanbanBoard {
constructor(container, options = {}) {
this.container = container;
this.options = {
draggable: true,
onCardMove: null,
onCardAdd: null,
onCardDelete: null,
...options
};
this.draggedCard = null;
this.init();
}
init() {
if (this.options.draggable) {
this.initDragAndDrop();
}
this.initAddCardButtons();
}
initDragAndDrop() {
const cards = this.container.querySelectorAll('.kanban-card');
const columns = this.container.querySelectorAll('.column-content');
cards.forEach(card => {
card.addEventListener('dragstart', (e) => this.handleDragStart(e, card));
card.addEventListener('dragend', (e) => this.handleDragEnd(e, card));
});
columns.forEach(column => {
column.addEventListener('dragover', (e) => this.handleDragOver(e));
column.addEventListener('drop', (e) => this.handleDrop(e, column));
column.addEventListener('dragenter', (e) => this.handleDragEnter(e, column));
column.addEventListener('dragleave', (e) => this.handleDragLeave(e, column));
});
}
handleDragStart(e, card) {
this.draggedCard = card;
card.classList.add('dragging');
e.dataTransfer.effectAllowed = 'move';
e.dataTransfer.setData('text/html', card.outerHTML);
}
handleDragEnd(e, card) {
card.classList.remove('dragging');
this.draggedCard = null;
// Remove all drop indicators
this.container.querySelectorAll('.drop-indicator').forEach(el => el.remove());
this.container.querySelectorAll('.drag-over').forEach(el => el.classList.remove('drag-over'));
}
handleDragOver(e) {
e.preventDefault();
e.dataTransfer.dropEffect = 'move';
}
handleDragEnter(e, column) {
column.classList.add('drag-over');
}
handleDragLeave(e, column) {
if (!column.contains(e.relatedTarget)) {
column.classList.remove('drag-over');
}
}
handleDrop(e, column) {
e.preventDefault();
column.classList.remove('drag-over');
if (this.draggedCard) {
const sourceColumn = this.draggedCard.closest('.column-content');
const targetColumn = column;
// Find drop position
const afterElement = this.getDragAfterElement(column, e.clientY);
if (afterElement) {
targetColumn.insertBefore(this.draggedCard, afterElement);
} else {
targetColumn.appendChild(this.draggedCard);
}
// Update card counts
this.updateCardCounts();
// Callback
if (this.options.onCardMove) {
this.options.onCardMove({
cardId: this.draggedCard.dataset.id,
fromColumn: sourceColumn.closest('.kanban-column').dataset.id,
toColumn: targetColumn.closest('.kanban-column').dataset.id
});
}
}
}
getDragAfterElement(column, y) {
const cards = [...column.querySelectorAll('.kanban-card:not(.dragging)')];
return cards.reduce((closest, card) => {
const box = card.getBoundingClientRect();
const offset = y - box.top - box.height / 2;
if (offset < 0 && offset > closest.offset) {
return { offset: offset, element: card };
} else {
return closest;
}
}, { offset: Number.NEGATIVE_INFINITY }).element;
}
updateCardCounts() {
this.container.querySelectorAll('.kanban-column').forEach(column => {
const count = column.querySelectorAll('.kanban-card').length;
const countEl = column.querySelector('.card-count');
if (countEl) {
countEl.textContent = count;
}
});
}
initAddCardButtons() {
this.container.querySelectorAll('.add-card-btn').forEach(btn => {
btn.addEventListener('click', () => {
const column = btn.closest('.kanban-column');
this.showAddCardForm(column);
});
});
}
showAddCardForm(column) {
// Implement your add card form logic
const title = prompt('Enter card title:');
if (title) {
this.addCard(column, { title });
}
}
addCard(column, cardData) {
const cardHtml = this.createCardElement(cardData);
const content = column.querySelector('.column-content');
content.insertAdjacentHTML('beforeend', cardHtml);
this.updateCardCounts();
if (this.options.draggable) {
this.initDragAndDrop();
}
if (this.options.onCardAdd) {
this.options.onCardAdd({
columnId: column.dataset.id,
card: cardData
});
}
}
createCardElement(cardData) {
return `
<div class="kanban-card" draggable="true" data-id="${Date.now()}">
<h4 class="card-title">${cardData.title}</h4>
</div>
`;
}
}
// Usage
const board = new KanbanBoard(document.querySelector('.kanban-board'), {
onCardMove: (data) => console.log('Card moved:', data),
onCardAdd: (data) => console.log('Card added:', data)
});React Component
import React, { useState, useRef } from 'react';
import './KanbanBoard.css';
const KanbanCard = ({
card,
draggable,
showLabels,
showAssignee,
showDueDate,
onDragStart,
onDragEnd,
labelColors
}) => {
const handleDragStart = (e) => {
onDragStart(e, card);
e.currentTarget.classList.add('dragging');
};
const handleDragEnd = (e) => {
onDragEnd(e, card);
e.currentTarget.classList.remove('dragging');
};
return (
<div
className="kanban-card"
draggable={draggable}
onDragStart={handleDragStart}
onDragEnd={handleDragEnd}
>
{showLabels && card.labels && (
<div className="card-labels">
{card.labels.map((label, i) => (
<span
key={i}
className="card-label"
style={{ background: labelColors[label] || '#ccc' }}
/>
))}
</div>
)}
<h4 className="card-title">{card.title}</h4>
<div className="card-footer">
{showDueDate && card.dueDate && (
<span className="card-due-date">{card.dueDate}</span>
)}
{showAssignee && card.assignee && (
<span className="card-assignee">{card.assignee}</span>
)}
</div>
<ToolFooter />
</div>
);
};
const KanbanColumn = ({
column,
draggable,
showCardCount,
showAddCard,
showLabels,
showAssignee,
showDueDate,
onDragStart,
onDragEnd,
onDragOver,
onDrop,
onAddCard,
labelColors
}) => {
const [isDragOver, setIsDragOver] = useState(false);
const handleDragOver = (e) => {
e.preventDefault();
onDragOver(e);
};
const handleDrop = (e) => {
e.preventDefault();
setIsDragOver(false);
onDrop(e, column.id);
};
return (
<div className="kanban-column">
<div className="column-header">
<h3 className="column-title">{column.title}</h3>
{showCardCount && (
<span className="card-count">{column.cards.length}</span>
)}
</div>
<div
className={`column-content ${isDragOver ? 'drag-over' : ''}`}
onDragOver={handleDragOver}
onDrop={handleDrop}
onDragEnter={() => setIsDragOver(true)}
onDragLeave={(e) => {
if (!e.currentTarget.contains(e.relatedTarget)) {
setIsDragOver(false);
}
}}
>
{column.cards.map(card => (
<KanbanCard
key={card.id}
card={card}
draggable={draggable}
showLabels={showLabels}
showAssignee={showAssignee}
showDueDate={showDueDate}
onDragStart={onDragStart}
onDragEnd={onDragEnd}
labelColors={labelColors}
/>
))}
</div>
{showAddCard && (
<button
className="add-card-btn"
onClick={() => onAddCard(column.id)}
>
+ Add a card
</button>
)}
</div>
);
};
const KanbanBoard = ({
initialColumns = [],
draggable = true,
showCardCount = true,
showAddCard = true,
showLabels = true,
showAssignee = true,
showDueDate = true,
labelColors = {},
onCardMove,
onCardAdd
}) => {
const [columns, setColumns] = useState(initialColumns);
const draggedCard = useRef(null);
const sourceColumn = useRef(null);
const handleDragStart = (e, card) => {
draggedCard.current = card;
sourceColumn.current = columns.find(col =>
col.cards.some(c => c.id === card.id)
)?.id;
e.dataTransfer.effectAllowed = 'move';
};
const handleDragEnd = () => {
draggedCard.current = null;
sourceColumn.current = null;
};
const handleDragOver = (e) => {
e.preventDefault();
e.dataTransfer.dropEffect = 'move';
};
const handleDrop = (e, targetColumnId) => {
if (!draggedCard.current || sourceColumn.current === targetColumnId) return;
const card = draggedCard.current;
const fromColumnId = sourceColumn.current;
setColumns(prev => prev.map(col => {
if (col.id === fromColumnId) {
return {
...col,
cards: col.cards.filter(c => c.id !== card.id)
};
}
if (col.id === targetColumnId) {
return {
...col,
cards: [...col.cards, card]
};
}
return col;
}));
onCardMove?.({
card,
fromColumn: fromColumnId,
toColumn: targetColumnId
});
};
const handleAddCard = (columnId) => {
const title = prompt('Enter card title:');
if (!title) return;
const newCard = {
id: Date.now(),
title,
labels: [],
assignee: null,
dueDate: null
};
setColumns(prev => prev.map(col => {
if (col.id === columnId) {
return {
...col,
cards: [...col.cards, newCard]
};
}
return col;
}));
onCardAdd?.({ columnId, card: newCard });
};
return (
<div className="kanban-board">
{columns.map(column => (
<KanbanColumn
key={column.id}
column={column}
draggable={draggable}
showCardCount={showCardCount}
showAddCard={showAddCard}
showLabels={showLabels}
showAssignee={showAssignee}
showDueDate={showDueDate}
onDragStart={handleDragStart}
onDragEnd={handleDragEnd}
onDragOver={handleDragOver}
onDrop={handleDrop}
onAddCard={handleAddCard}
labelColors={labelColors}
/>
))}
</div>
);
};
export default KanbanBoard;