Section
Kanban Board — Vercel
Refined dark monochrome project kanban board with hairline borders, a subtle radial glow, a single teal status accent, and count pills. Draggable task cards move between Backlog, In Progress, Review, and Done via the HTML5 Drag and Drop API, count badges update live, and an add-card affordance creates new tasks per column. Generous spacing and a premium dev-tool aesthetic.
- Section
- Tailwind
- HTML
- MIT
Preview
Live section
9:41 100%
devsnips.dev/library/tailwind/sections/app-ui/kanban-board/vercel/ fluid · no fixed width 100%
Install
Add to your project
npx devsnips add Tailwind/Sections/App-UI/kanban-board/vercel Tailwind/Sections/App-UI/kanban-board/vercel <!--
Snippet Name: Kanban Board — Vercel
Description: Dark monochrome kanban board with hairline borders, a glowing teal status, count pills, draggable task cards (HTML5 DnD), and an add-card affordance per column.
Author: DevSnips Contributors
Usage Example: Drop into a dark product shell as a project board widget. Drag cards between columns and click "+ Add card" to create new tasks. Fully wired in preview.html.
-->
<div class="relative w-full" data-board="vc">
<!-- Board panel -->
<div class="overflow-hidden rounded-[14px] border border-white/10 bg-[#0a0a0a] shadow-[0_0_0_1px_rgba(255,255,255,0.02),0_20px_60px_-20px_rgba(0,0,0,0.9)]">
<div class="pointer-events-none absolute -inset-px -z-10 rounded-[14px] opacity-60 [background:radial-gradient(60%_60%_at_50%_0%,rgba(255,255,255,0.10),transparent_70%)]" aria-hidden="true"></div>
<div class="board-cols flex gap-4 overflow-x-auto p-4 sm:p-5" data-board-cols>
<!-- Column -->
<section class="vc-col w-[280px] shrink-0 rounded-xl border border-white/10 bg-white/[0.02]" aria-label="Backlog">
<header class="flex items-center justify-between border-b border-white/[0.07] px-3.5 py-2.5">
<div class="flex items-center gap-2">
<span class="vc-dot"></span>
<h3 class="font-g text-[13px] font-medium text-white">Backlog</h3>
</div>
<span class="vc-count vc-mono rounded-full border border-white/10 bg-white/[0.05] px-2 py-0.5 text-[11px] text-white/55" data-count>3</span>
</header>
<div class="vc-list space-y-2.5 p-2.5" data-list>
<article class="vc-card group cursor-grab rounded-lg border border-white/10 bg-[#0a0a0a] p-3 transition-colors hover:border-white/20 active:cursor-grabbing" draggable="true" tabindex="0" data-id="SNIP-104">
<div class="mb-2 flex items-center justify-between gap-2">
<span class="vc-mono rounded-md border border-white/10 bg-white/[0.06] px-1.5 py-0.5 text-[10px] font-medium uppercase text-white/80">P1</span>
<span class="vc-mono text-[10px] text-white/35">SNIP-104</span>
</div>
<h4 class="font-g text-[13.5px] font-medium leading-snug text-white">Wire dark-mode toggle to root class</h4>
<p class="mt-1 font-g text-[12px] leading-relaxed text-white/45">Persist choice in localStorage and reflect on first paint.</p>
<div class="mt-3 flex items-center justify-between">
<div class="flex flex-wrap items-center gap-1">
<span class="vc-code">css</span>
<span class="vc-code">a11y</span>
</div>
<div class="flex h-6 w-6 items-center justify-center rounded-full border border-white/10 bg-white/[0.06] vc-mono text-[10px] font-medium text-white/80">RM</div>
</div>
</article>
<article class="vc-card group cursor-grab rounded-lg border border-white/10 bg-[#0a0a0a] p-3 transition-colors hover:border-white/20 active:cursor-grabbing" draggable="true" tabindex="0" data-id="SNIP-118">
<div class="mb-2 flex items-center justify-between gap-2">
<span class="vc-mono rounded-md border border-white/10 bg-white/[0.06] px-1.5 py-0.5 text-[10px] font-medium uppercase text-white/80">P3</span>
<span class="vc-mono text-[10px] text-white/35">SNIP-118</span>
</div>
<h4 class="font-g text-[13.5px] font-medium leading-snug text-white">Sketch onboarding empty state</h4>
<p class="mt-1 font-g text-[12px] leading-relaxed text-white/45">Three steps with inline illustrations and a CTA.</p>
<div class="mt-3 flex items-center justify-between">
<div class="flex flex-wrap items-center gap-1">
<span class="vc-code">design</span>
</div>
<div class="flex h-6 w-6 items-center justify-center rounded-full border border-white/10 bg-white/[0.06] vc-mono text-[10px] font-medium text-white/80">JL</div>
</div>
</article>
<article class="vc-card group cursor-grab rounded-lg border border-white/10 bg-[#0a0a0a] p-3 transition-colors hover:border-white/20 active:cursor-grabbing" draggable="true" tabindex="0" data-id="SNIP-121">
<div class="mb-2 flex items-center justify-between gap-2">
<span class="vc-mono rounded-md border border-white/10 bg-white/[0.06] px-1.5 py-0.5 text-[10px] font-medium uppercase text-white/80">P2</span>
<span class="vc-mono text-[10px] text-white/35">SNIP-121</span>
</div>
<h4 class="font-g text-[13.5px] font-medium leading-snug text-white">Research drag-drop a11y patterns</h4>
<p class="mt-1 font-g text-[12px] leading-relaxed text-white/45">Compare native DnD vs roving tabindex keyboards.</p>
<div class="mt-3 flex items-center justify-between">
<div class="flex flex-wrap items-center gap-1">
<span class="vc-code">a11y</span>
<span class="vc-code">research</span>
</div>
<div class="flex h-6 w-6 items-center justify-center rounded-full border border-white/10 bg-white/[0.06] vc-mono text-[10px] font-medium text-white/80">SK</div>
</div>
</article>
</div>
<button type="button" class="vc-add m-2.5 mt-0 flex w-[calc(100%-1.25rem)] items-center justify-center gap-1.5 rounded-lg border border-dashed border-white/10 bg-transparent py-2 vc-mono text-[11px] text-white/45 transition-colors hover:border-white/20 hover:text-white focus:outline-none focus-visible:ring-2 focus-visible:ring-white/30">
<svg class="h-3.5 w-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M12 5v14M5 12h14" /></svg>
Add card
</button>
</section>
<!-- Column -->
<section class="vc-col w-[280px] shrink-0 rounded-xl border border-white/10 bg-white/[0.02]" aria-label="In Progress">
<header class="flex items-center justify-between border-b border-white/[0.07] px-3.5 py-2.5">
<div class="flex items-center gap-2">
<span class="vc-dot"></span>
<h3 class="font-g text-[13px] font-medium text-white">In Progress</h3>
</div>
<span class="vc-count vc-mono rounded-full border border-white/10 bg-white/[0.05] px-2 py-0.5 text-[11px] text-white/55" data-count>2</span>
</header>
<div class="vc-list space-y-2.5 p-2.5" data-list>
<article class="vc-card group cursor-grab rounded-lg border border-white/10 bg-[#0a0a0a] p-3 transition-colors hover:border-white/20 active:cursor-grabbing" draggable="true" tabindex="0" data-id="SNIP-098">
<div class="mb-2 flex items-center justify-between gap-2">
<span class="vc-mono rounded-md border border-white/10 bg-white/[0.06] px-1.5 py-0.5 text-[10px] font-medium uppercase text-white/80">P1</span>
<span class="vc-mono text-[10px] text-white/35">SNIP-098</span>
</div>
<h4 class="font-g text-[13.5px] font-medium leading-snug text-white">Ship kanban drag-and-drop widget</h4>
<p class="mt-1 font-g text-[12px] leading-relaxed text-white/45">HTML5 DnD, move cards across columns, update counts live.</p>
<div class="mt-3 flex items-center justify-between">
<div class="flex flex-wrap items-center gap-1">
<span class="vc-code">js</span>
<span class="vc-code">widget</span>
</div>
<div class="flex h-6 w-6 items-center justify-center rounded-full border border-white/10 bg-white/[0.06] vc-mono text-[10px] font-medium text-white/80">AB</div>
</div>
</article>
<article class="vc-card group cursor-grab rounded-lg border border-white/10 bg-[#0a0a0a] p-3 transition-colors hover:border-white/20 active:cursor-grabbing" draggable="true" tabindex="0" data-id="SNIP-110">
<div class="mb-2 flex items-center justify-between gap-2">
<span class="vc-mono rounded-md border border-white/10 bg-white/[0.06] px-1.5 py-0.5 text-[10px] font-medium uppercase text-white/80">P2</span>
<span class="vc-mono text-[10px] text-white/35">SNIP-110</span>
</div>
<h4 class="font-g text-[13.5px] font-medium leading-snug text-white">Tokenize the 3-style palette</h4>
<p class="mt-1 font-g text-[12px] leading-relaxed text-white/45">Extract shared tokens so variants stay cohesive.</p>
<div class="mt-3 flex items-center justify-between">
<div class="flex flex-wrap items-center gap-1">
<span class="vc-code">tokens</span>
</div>
<div class="flex h-6 w-6 items-center justify-center rounded-full border border-white/10 bg-white/[0.06] vc-mono text-[10px] font-medium text-white/80">DT</div>
</div>
</article>
</div>
<button type="button" class="vc-add m-2.5 mt-0 flex w-[calc(100%-1.25rem)] items-center justify-center gap-1.5 rounded-lg border border-dashed border-white/10 bg-transparent py-2 vc-mono text-[11px] text-white/45 transition-colors hover:border-white/20 hover:text-white focus:outline-none focus-visible:ring-2 focus-visible:ring-white/30">
<svg class="h-3.5 w-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M12 5v14M5 12h14" /></svg>
Add card
</button>
</section>
<!-- Column -->
<section class="vc-col w-[280px] shrink-0 rounded-xl border border-white/10 bg-white/[0.02]" aria-label="Review">
<header class="flex items-center justify-between border-b border-white/[0.07] px-3.5 py-2.5">
<div class="flex items-center gap-2">
<span class="vc-dot"></span>
<h3 class="font-g text-[13px] font-medium text-white">Review</h3>
</div>
<span class="vc-count vc-mono rounded-full border border-white/10 bg-white/[0.05] px-2 py-0.5 text-[11px] text-white/55" data-count>1</span>
</header>
<div class="vc-list space-y-2.5 p-2.5" data-list>
<article class="vc-card group cursor-grab rounded-lg border border-white/10 bg-[#0a0a0a] p-3 transition-colors hover:border-white/20 active:cursor-grabbing" draggable="true" tabindex="0" data-id="SNIP-091">
<div class="mb-2 flex items-center justify-between gap-2">
<span class="vc-mono rounded-md border border-white/10 bg-white/[0.06] px-1.5 py-0.5 text-[10px] font-medium uppercase text-white/80">P3</span>
<span class="vc-mono text-[10px] text-white/35">SNIP-091</span>
</div>
<h4 class="font-g text-[13.5px] font-medium leading-snug text-white">Audit focus rings across sections</h4>
<p class="mt-1 font-g text-[12px] leading-relaxed text-white/45">Ensure every interactive element has a visible focus-visible ring.</p>
<div class="mt-3 flex items-center justify-between">
<div class="flex flex-wrap items-center gap-1">
<span class="vc-code">a11y</span>
<span class="vc-code">audit</span>
</div>
<div class="flex h-6 w-6 items-center justify-center rounded-full border border-white/10 bg-white/[0.06] vc-mono text-[10px] font-medium text-white/80">RM</div>
</div>
</article>
</div>
<button type="button" class="vc-add m-2.5 mt-0 flex w-[calc(100%-1.25rem)] items-center justify-center gap-1.5 rounded-lg border border-dashed border-white/10 bg-transparent py-2 vc-mono text-[11px] text-white/45 transition-colors hover:border-white/20 hover:text-white focus:outline-none focus-visible:ring-2 focus-visible:ring-white/30">
<svg class="h-3.5 w-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M12 5v14M5 12h14" /></svg>
Add card
</button>
</section>
<!-- Column -->
<section class="vc-col w-[280px] shrink-0 rounded-xl border border-white/10 bg-white/[0.02]" aria-label="Done">
<header class="flex items-center justify-between border-b border-white/[0.07] px-3.5 py-2.5">
<div class="flex items-center gap-2">
<span class="vc-dot"></span>
<h3 class="font-g text-[13px] font-medium text-white/60">Done</h3>
</div>
<span class="vc-count vc-mono rounded-full border border-white/10 bg-white/[0.05] px-2 py-0.5 text-[11px] text-white/55" data-count>2</span>
</header>
<div class="vc-list space-y-2.5 p-2.5" data-list>
<article class="vc-card group cursor-grab rounded-lg border border-white/10 bg-[#0a0a0a] p-3 transition-colors hover:border-white/20 active:cursor-grabbing" draggable="true" tabindex="0" data-id="SNIP-087">
<div class="mb-2 flex items-center justify-between gap-2">
<span class="vc-mono rounded-md border border-white/10 bg-white/[0.06] px-1.5 py-0.5 text-[10px] font-medium uppercase text-white/80">P3</span>
<span class="vc-mono text-[10px] text-white/35">SNIP-087</span>
</div>
<h4 class="font-g text-[13.5px] font-medium leading-snug text-white/55 line-through decoration-white/30">Set up CDN + fonts scaffold</h4>
<p class="mt-1 font-g text-[12px] leading-relaxed text-white/35">Tailwind play CDN + Google Fonts across variants.</p>
<div class="mt-3 flex items-center justify-between">
<div class="flex flex-wrap items-center gap-1">
<span class="vc-code">infra</span>
</div>
<div class="flex h-6 w-6 items-center justify-center rounded-full border border-white/10 bg-white/[0.06] vc-mono text-[10px] font-medium text-white/80">JL</div>
</div>
</article>
<article class="vc-card group cursor-grab rounded-lg border border-white/10 bg-[#0a0a0a] p-3 transition-colors hover:border-white/20 active:cursor-grabbing" draggable="true" tabindex="0" data-id="SNIP-074">
<div class="mb-2 flex items-center justify-between gap-2">
<span class="vc-mono rounded-md border border-white/10 bg-white/[0.06] px-1.5 py-0.5 text-[10px] font-medium uppercase text-white/80">P3</span>
<span class="vc-mono text-[10px] text-white/35">SNIP-074</span>
</div>
<h4 class="font-g text-[13.5px] font-medium leading-snug text-white/55 line-through decoration-white/30">Write STYLE_TOKENS reference</h4>
<p class="mt-1 font-g text-[12px] leading-relaxed text-white/35">Canonical token doc for all three design styles.</p>
<div class="mt-3 flex items-center justify-between">
<div class="flex flex-wrap items-center gap-1">
<span class="vc-code">docs</span>
</div>
<div class="flex h-6 w-6 items-center justify-center rounded-full border border-white/10 bg-white/[0.06] vc-mono text-[10px] font-medium text-white/80">SK</div>
</div>
</article>
</div>
<button type="button" class="vc-add m-2.5 mt-0 flex w-[calc(100%-1.25rem)] items-center justify-center gap-1.5 rounded-lg border border-dashed border-white/10 bg-transparent py-2 vc-mono text-[11px] text-white/45 transition-colors hover:border-white/20 hover:text-white focus:outline-none focus-visible:ring-2 focus-visible:ring-white/30">
<svg class="h-3.5 w-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M12 5v14M5 12h14" /></svg>
Add card
</button>
</section>
</div>
</div>
<style>
[data-board="vc"] .vc-mono { font-family: 'Geist Mono', ui-monospace, monospace; }
[data-board="vc"] .font-g { font-family: 'Geist', sans-serif; }
[data-board="vc"] .vc-code { font-family: 'Geist Mono', monospace; font-size: 11px; background: rgba(255,255,255,0.06); padding: 1px 6px; border-radius: 5px; color: rgba(255,255,255,0.75); }
[data-board="vc"] .vc-dot { display: inline-block; height: 6px; width: 6px; border-radius: 9999px; background: #50e3c2; box-shadow: 0 0 8px #50e3c2; vertical-align: middle; }
[data-board="vc"] .vc-list { min-height: 48px; transition: background-color .12s ease; border-radius: 8px; }
[data-board="vc"] .vc-list.vc-over { background-color: rgba(80, 227, 194, 0.06); outline: 1px dashed rgba(80,227,194,0.5); outline-offset: -2px; }
[data-board="vc"] .vc-card.vc-drag { opacity: .35; }
[data-board="vc"] .vc-card:focus { outline: 2px solid rgba(255,255,255,0.4); outline-offset: 2px; }
[data-board="vc"] .vc-placeholder { border: 1px dashed rgba(255,255,255,0.25); background: rgba(255,255,255,0.03); height: 78px; border-radius: 8px; }
[data-board="vc"] .board-cols { scrollbar-width: thin; scrollbar-color: rgba(255,255,255,0.15) transparent; }
[data-board="vc"] .board-cols::-webkit-scrollbar { height: 8px; }
[data-board="vc"] .board-cols::-webkit-scrollbar-track { background: transparent; }
[data-board="vc"] .board-cols::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.12); border-radius: 9999px; }
</style>
<script>
(function () {
var root = document.currentScript.closest('[data-board="vc"]');
if (!root) return;
var cols = root.querySelectorAll('[data-list]');
var dragged = null;
var placeholder = null;
var uid = 0;
function updateCounts() {
root.querySelectorAll('.vc-col').forEach(function (col) {
var count = col.querySelector('[data-list]').children.length;
var badge = col.querySelector('[data-count]');
if (badge) badge.textContent = count;
});
}
function makePlaceholder() {
var p = document.createElement('div');
p.className = 'vc-placeholder';
return p;
}
function clearOver() {
cols.forEach(function (l) { l.classList.remove('vc-over'); });
}
function attachCard(card) {
card.addEventListener('dragstart', function (e) {
dragged = card;
card.classList.add('vc-drag');
if (e.dataTransfer) {
e.dataTransfer.effectAllowed = 'move';
try { e.dataTransfer.setData('text/plain', card.dataset.id || ''); } catch (err) {}
}
placeholder = makePlaceholder();
if (card.parentNode) card.parentNode.insertBefore(placeholder, card.nextSibling);
});
card.addEventListener('dragend', function () {
card.classList.remove('vc-drag');
if (placeholder && placeholder.parentNode) placeholder.parentNode.removeChild(placeholder);
placeholder = null;
dragged = null;
clearOver();
updateCounts();
});
}
root.querySelectorAll('.vc-card').forEach(attachCard);
cols.forEach(function (list) {
list.addEventListener('dragover', function (e) {
e.preventDefault();
if (e.dataTransfer) e.dataTransfer.dropEffect = 'move';
list.classList.add('vc-over');
if (!dragged || !placeholder) return;
var after = null;
var cards = list.querySelectorAll('.vc-card:not(.vc-drag)');
Array.prototype.forEach.call(cards, function (c) {
var r = c.getBoundingClientRect();
if (e.clientY < r.top + r.height / 2) { after = c; }
});
if (after) { list.insertBefore(placeholder, after); }
else if (list !== dragged.parentNode || list.lastElementChild !== placeholder) { list.appendChild(placeholder); }
});
list.addEventListener('dragleave', function (e) {
if (!list.contains(e.relatedTarget)) list.classList.remove('vc-over');
});
list.addEventListener('drop', function (e) {
e.preventDefault();
if (dragged && placeholder && placeholder.parentNode === list) {
list.insertBefore(dragged, placeholder);
if (placeholder.parentNode) placeholder.parentNode.removeChild(placeholder);
} else if (dragged) {
list.appendChild(dragged);
}
placeholder = null;
dragged = null;
clearOver();
updateCounts();
});
});
var avatars = ['AB', 'CD', 'EF', 'GH', 'JK', 'MN'];
root.querySelectorAll('.vc-add').forEach(function (btn) {
btn.addEventListener('click', function () {
var col = btn.closest('.vc-col');
var list = col.querySelector('[data-list]');
uid += 1;
var id = 'SNIP-' + (200 + uid);
var card = document.createElement('article');
card.className = 'vc-card group cursor-grab rounded-lg border border-white/10 bg-[#0a0a0a] p-3 transition-colors hover:border-white/20 active:cursor-grabbing';
card.setAttribute('draggable', 'true');
card.setAttribute('tabindex', '0');
card.dataset.id = id;
card.innerHTML =
'<div class="mb-2 flex items-center justify-between gap-2">' +
'<span class="vc-mono rounded-md border border-white/10 bg-white/[0.06] px-1.5 py-0.5 text-[10px] font-medium uppercase text-white/80">P2</span>' +
'<span class="vc-mono text-[10px] text-white/35">' + id + '</span>' +
'</div>' +
'<h4 class="font-g text-[13.5px] font-medium leading-snug text-white">New task — define the next step</h4>' +
'<p class="mt-1 font-g text-[12px] leading-relaxed text-white/45">Click to flesh out details, owners, and tags.</p>' +
'<div class="mt-3 flex items-center justify-between">' +
'<div class="flex flex-wrap items-center gap-1"><span class="vc-code">new</span></div>' +
'<div class="flex h-6 w-6 items-center justify-center rounded-full border border-white/10 bg-white/[0.06] vc-mono text-[10px] font-medium text-white/80">' + avatars[uid % avatars.length] + '</div>' +
'</div>';
list.appendChild(card);
attachCard(card);
updateCounts();
card.focus();
});
});
// Keyboard reordering: ArrowUp/Down moves within a column,
// ArrowLeft/Right moves the card to the previous/next column.
// Reuses the same DOM-move + updateCounts() logic as drag-and-drop.
root.querySelectorAll('.vc-card').forEach(function (card) {
card.addEventListener('keydown', function (e) {
var list = card.parentNode;
if (!list || !list.hasAttribute('data-list')) return;
var colsArr = Array.prototype.slice.call(cols);
var colIdx = colsArr.indexOf(list);
var cards = Array.prototype.slice.call(list.querySelectorAll('.vc-card'));
var idx = cards.indexOf(card);
function moveTo(targetList) {
targetList.appendChild(card); updateCounts(); card.focus();
}
switch (e.key) {
case 'ArrowUp':
e.preventDefault();
if (idx > 0) { list.insertBefore(card, cards[idx - 1]); updateCounts(); card.focus(); }
break;
case 'ArrowDown':
e.preventDefault();
if (idx < cards.length - 1) { list.insertBefore(card, cards[idx + 1].nextSibling); updateCounts(); card.focus(); }
break;
case 'ArrowLeft':
e.preventDefault();
if (colIdx > 0) { moveTo(colsArr[colIdx - 1]); }
break;
case 'ArrowRight':
e.preventDefault();
if (colIdx < colsArr.length - 1) { moveTo(colsArr[colIdx + 1]); }
break;
}
});
});
updateCounts();
})();
</script>
</div> 374 lines UTF-8 · LF · Spaces: 2
Continue browsing