Kanban board

칸반 보드

A board of status columns — To do, Doing, Done — where dragging a card between them tracks its progress.

Also known as: Task boardColumn board
···
html
<div class="kbw">
  <div class="kbcol"><h3>할 일 <span>2</span></h3>
    <div class="kbcard" draggable="true">디자인 리뷰</div>
    <div class="kbcard" draggable="true">API 문서 작성</div>
  </div>
  <div class="kbcol"><h3>진행 중 <span>1</span></h3>
    <div class="kbcard" draggable="true">결제 연동</div>
  </div>
  <div class="kbcol"><h3>완료 <span>1</span></h3>
    <div class="kbcard" draggable="true">로그인 화면</div>
  </div>
</div>
css
.kbw{position:absolute;inset:8px;display:flex;gap:7px}
.kbcol{flex:1;background:var(--surface);border:1px solid var(--line);border-radius:10px;padding:7px;display:flex;flex-direction:column;gap:6px;min-width:0}
.kbcol h3{margin:0;font-size:9px;color:var(--muted);display:flex;justify-content:space-between;font-weight:700;text-transform:uppercase;letter-spacing:.03em}
.kbcol h3 span{background:var(--line);color:var(--fg);border-radius:999px;padding:0 5px}
.kbcard{background:var(--bg);border:1px solid var(--line);border-radius:8px;padding:6px 8px;font-size:10px;color:var(--fg);cursor:grab;transition:transform .25s,opacity .25s}
.kbcard[data-moving]{opacity:.4;transform:scale(.94)}
js
const cols = [...document.querySelectorAll('.kbcol')];
let dragEl = null;
document.querySelectorAll('.kbcard').forEach((c) => {
  c.addEventListener('dragstart', () => { auto = false; dragEl = c; c.setAttribute('data-moving', ''); });
  c.addEventListener('dragend', () => c.removeAttribute('data-moving'));
});
cols.forEach((col) => {
  col.addEventListener('dragover', (e) => e.preventDefault());
  col.addEventListener('drop', () => { if (dragEl) col.appendChild(dragEl); updateCounts(); });
});
function updateCounts() { cols.forEach((col) => { col.querySelector('span').textContent = String(col.querySelectorAll('.kbcard').length); }); }
let auto = true;
function autoMove() {
  if (!auto) return;
  const from = cols[Math.floor(Math.random() * 2)];
  const cards = from.querySelectorAll('.kbcard');
  if (!cards.length) { setTimeout(autoMove, 1600); return; }
  const card = cards[0];
  const to = cols[(cols.indexOf(from) + 1) % cols.length];
  card.setAttribute('data-moving', '');
  setTimeout(() => {
    if (!auto) return;
    to.appendChild(card);
    card.removeAttribute('data-moving');
    updateCounts();
    setTimeout(autoMove, 1600);
  }, 260);
}
setTimeout(autoMove, 1200);

Native HTML5 drag and drop (draggable="true", dragstart/dragover/drop) makes moving a card between columns cheap to build, but the API is mouse-first and unusable with just a keyboard or a screen reader — a fully accessible version needs a keyboard-operable fallback per card too, like a "Move to next column" button.

The difference from a sortable list is what changes — a sortable list only reorders items within one list (their rank), while a kanban board moves a card into a different column, changing its status (category). Compared with a data table, a data table lines up the same attributes as rows for easy side-by-side comparison, while a kanban board is stronger at showing flow and how much has piled up at each stage, spatially.

Putting a live count in each column's header — how many cards sit there right now — surfaces a bottleneck at a glance, without counting cards one by one.