<div class="kanban-dashboard">
<div class="dashboard-header">
<h2>Project Roadmap</h2>
<p>Drag and drop tasks to organize your workflow.</p>
</div>
<div class="kanban-wrapper">
<!-- Column 1 -->
<div class="kanban-column">
<div class="column-header">
<h3>To Do</h3>
<span class="count">2</span>
</div>
<div class="task-list">
<div class="kanban-task" draggable="true">
<div class="tag design">Design</div>
<p>Create modern wireframes</p>
<img src="https://placehold.co/24x24/3b82f6/ffffff?text=A" class="avatar" alt="Avatar">
</div>
<div class="kanban-task" draggable="true">
<div class="tag dev">Development</div>
<p>Set up database schemas</p>
<img src="https://placehold.co/24x24/10b981/ffffff?text=B" class="avatar" alt="Avatar">
</div>
</div>
</div>
<!-- Column 2 -->
<div class="kanban-column">
<div class="column-header">
<h3>In Progress</h3>
<span class="count">1</span>
</div>
<div class="task-list">
<div class="kanban-task" draggable="true">
<div class="tag dev">Development</div>
<p>Build drag and drop logic</p>
<img src="https://placehold.co/24x24/3b82f6/ffffff?text=A" class="avatar" alt="Avatar">
</div>
</div>
</div>
<!-- Column 3 -->
<div class="kanban-column">
<div class="column-header">
<h3>Completed</h3>
<span class="count">0</span>
</div>
<div class="task-list"></div>
</div>
</div>
</div>
*, *::before, *::after {
box-sizing: border-box;
}
body {
font-family: system-ui, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
background: #f8fafc;
padding: 40px 20px;
margin: 0;
display: flex;
justify-content: center;
min-height: 100vh;
}
.kanban-dashboard {
width: 100%;
max-width: 1100px;
}
.dashboard-header {
margin-bottom: 30px;
}
.dashboard-header h2 {
margin: 0 0 8px 0;
color: #0f172a;
font-size: 2rem;
}
.dashboard-header p {
margin: 0;
color: #64748b;
}
.kanban-wrapper {
display: flex;
gap: 24px;
align-items: flex-start;
overflow-x: auto;
padding-bottom: 20px;
}
.kanban-column {
flex: 1;
min-width: 300px;
background: #f1f5f9;
border-radius: 16px;
padding: 20px;
display: flex;
flex-direction: column;
}
.column-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.column-header h3 {
margin: 0;
color: #1e293b;
font-size: 1.125rem;
}
.count {
background: #e2e8f0;
color: #475569;
padding: 4px 12px;
border-radius: 20px;
font-size: 0.875rem;
font-weight: 600;
}
.task-list {
display: flex;
flex-direction: column;
gap: 16px;
min-height: 150px;
}
.kanban-task {
background: #ffffff;
padding: 20px;
border-radius: 12px;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.05);
cursor: grab;
transition: transform 0.2s, box-shadow 0.2s;
border: 1px solid #e2e8f0;
position: relative;
}
.kanban-task:active {
cursor: grabbing;
}
.kanban-task.dragging {
opacity: 0.6;
transform: scale(0.95) rotate(2deg);
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1);
border-color: #3b82f6;
}
.tag {
display: inline-block;
padding: 4px 10px;
border-radius: 6px;
font-size: 0.75rem;
font-weight: 600;
margin-bottom: 12px;
}
.tag.design { background: #fce7f3; color: #db2777; }
.tag.dev { background: #dbeafe; color: #2563eb; }
.kanban-task p {
margin: 0 0 20px 0;
color: #334155;
font-weight: 500;
line-height: 1.5;
}
.avatar {
border-radius: 50%;
position: absolute;
bottom: 20px;
right: 20px;
}
const tasks = document.querySelectorAll(".kanban-task");
const lists = document.querySelectorAll(".task-list");
let activeTask = null;
tasks.forEach(function(task) {
task.addEventListener("dragstart", function() {
activeTask = task;
setTimeout(function() {
task.classList.add("dragging");
}, 0);
});
task.addEventListener("dragend", function() {
activeTask.classList.remove("dragging");
activeTask = null;
});
});
lists.forEach(function(list) {
list.addEventListener("dragover", function(e) {
e.preventDefault();
let closestTask = null;
let closestOffset = Number.NEGATIVE_INFINITY;
// Find all tasks in the list that are NOT currently being dragged
const siblings = list.querySelectorAll(".kanban-task:not(.dragging)");
// Determine exactly where the cursor is relative to the other tasks
siblings.forEach(function(sibling) {
const box = sibling.getBoundingClientRect();
const offset = e.clientY - box.top - box.height / 2;
if (offset < 0 && offset > closestOffset) {
closestOffset = offset;
closestTask = sibling;
}
});
if (activeTask) {
if (closestTask == null) {
// Drop at the bottom if no task is below the cursor
list.appendChild(activeTask);
} else {
// Insert before the task we are hovering over
list.insertBefore(activeTask, closestTask);
}
}
});
});