칸반 보드

Kanban board

할 일 → 진행 중 → 완료처럼 상태를 나타내는 여러 열에 카드를 드래그로 옮겨 진행 상황을 관리하는 보드.

다른 이름: 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);

네이티브 HTML5 드래그 앤 드롭(draggable="true", dragstart/dragover/drop)으로 구현하면 카드를 다른 열로 옮기는 것 자체는 가볍게 만들 수 있지만, 이 API는 마우스 기준이라 키보드나 스크린리더만으로는 조작할 수 없다는 한계가 있습니다 — 완전히 접근 가능하게 만들려면 카드마다 "다음 열로 이동" 같은 키보드로 실행 가능한 대안 버튼을 함께 둬야 합니다.

정렬 가능한 목록과 다른 점은 무엇이 바뀌느냐입니다 — 정렬 가능한 목록은 같은 목록 안에서 항목의 순서(몇 번째냐)만 바꾸지만, 칸반 보드는 카드를 다른 열로 옮겨 그 카드의 상태(분류)를 바꿉니다. 데이터 테이블과 비교하면, 데이터 테이블은 같은 속성들을 행으로 늘어놓아 한눈에 비교하기 좋고, 칸반 보드는 진행 흐름과 각 단계에 쌓인 양을 공간적으로 보여주는 데 강합니다.

각 열 제목에 지금 카드가 몇 개 있는지 숫자를 함께 두면, 어느 열에 일이 몰려 있는지(병목 지점)가 카드를 하나하나 세지 않아도 바로 보입니다.