Kanbanbord met slepen New
Task board with three columns where you drag cards with mouse or finger (pointer events, works on touch). With the keyboard you pick up a card with Space, move it with the arrow keys and drop it with Space; Escape puts it back. A live region announces every step. For client portals, project overviews and internal planning.
Verbouwing keuken · fam. Yilmaz, Utrecht
Sleep een kaart, of pak hem op met Spatie
Te doen 3
- Tegels kiezen
- Offerte stucwerk
- Afvoer verleggen
Bezig 2
- Keuken slopen
- Groep bijplaatsen
Klaar 2
- Keuken inmeten
- Aanbetaling € 2.400
Spatie pakt de kaart op. Pijltjes verplaatsen hem, Spatie zet hem neer, Escape annuleert.
How to use it
- Paste the HTML where the numbers & charts should appear.
- Add the CSS to your stylesheet. All classes are prefixed, so nothing clashes with your own styles.
- Add the JavaScript and call init() with the element that contains the HTML.
<!-- Kanbanbord met slepen · NedDev · eigen werk. -->
<div class="jouw-container">
<div class="kbn">
<div class="kbn__hd"><p class="kbn__t">Verbouwing keuken · fam. Yilmaz, Utrecht</p><p class="kbn__hint">Sleep een kaart, of pak hem op met Spatie</p></div>
<div class="kbn__cols">
<div class="kbn__col" data-col="todo"><p class="kbn__ch">Te doen <span class="kbn__n">3</span></p><ul class="kbn__list" aria-label="Te doen"><li class="kbn__card" tabindex="0">Tegels kiezen</li><li class="kbn__card" tabindex="0">Offerte stucwerk</li><li class="kbn__card" tabindex="0">Afvoer verleggen</li></ul></div>
<div class="kbn__col" data-col="bezig"><p class="kbn__ch">Bezig <span class="kbn__n">2</span></p><ul class="kbn__list" aria-label="Bezig"><li class="kbn__card" tabindex="0">Keuken slopen</li><li class="kbn__card" tabindex="0">Groep bijplaatsen</li></ul></div>
<div class="kbn__col" data-col="klaar"><p class="kbn__ch">Klaar <span class="kbn__n">2</span></p><ul class="kbn__list" aria-label="Klaar"><li class="kbn__card" tabindex="0">Keuken inmeten</li><li class="kbn__card" tabindex="0">Aanbetaling € 2.400</li></ul></div>
</div>
<p class="kbn__sr" aria-live="assertive"></p>
<p class="kbn__sr kbn__help">Spatie pakt de kaart op. Pijltjes verplaatsen hem, Spatie zet hem neer, Escape annuleert.</p>
</div>
</div>/* Kanbanbord met slepen · NedDev · eigen werk. */
.kbn{position:absolute;inset:0;box-sizing:border-box;padding:10px 12px;background:#f4f3f1;color:#0a0a0a;font-family:'Switzer',sans-serif;display:flex;flex-direction:column;gap:8px;container-type:inline-size}
.kbn__hd{display:flex;flex-direction:column;gap:1px}
.kbn__t{margin:0;font-size:13px;font-weight:600;line-height:1.3}
.kbn__hint{margin:0;font-size:12px;color:#5f5e5a}
.kbn__cols{display:flex;gap:8px;flex:1;min-height:0}
.kbn__col{flex:1 1 0;min-width:0;background:#e8e6e2;border-radius:8px;padding:6px;display:flex;flex-direction:column;gap:6px;overflow:hidden}
.kbn__ch{margin:0;padding:0 2px;font-size:12px;font-weight:600;color:#3d3c39;display:flex;justify-content:space-between}
.kbn__n{font-weight:500;color:#6b6a66;font-variant-numeric:tabular-nums}
.kbn__list{list-style:none;margin:0;padding:1px;display:flex;flex-direction:column;gap:5px;flex:1;min-height:24px;overflow-y:auto;border-radius:4px;transition:background-color .15s}
.kbn__list.is-over{background:#f9dccf}
.kbn__card{box-sizing:border-box;background:#fff;border:1px solid #d6d3ce;border-radius:4px;padding:6px 7px;font-size:12px;line-height:1.3;cursor:grab;touch-action:none;user-select:none;-webkit-user-select:none;outline:none;transition:transform .15s,border-color .15s,background-color .15s}
.kbn__card:hover{border-color:#8a8883}
.kbn__card:focus-visible{outline:2px solid #F5480F;outline-offset:1px}
.kbn__card.is-grab{border-color:#F5480F;background:#fff4ef;transform:rotate(-1.5deg)}
.kbn__col[data-col="klaar"] .kbn__card{color:#6b6a66}
.kbn__ph{box-sizing:border-box;border:1.5px dashed #F5480F;border-radius:4px;background:#fdebe4;flex:none}
.kbn__ghost{position:absolute;z-index:5;margin:0;pointer-events:none;cursor:grabbing;border-color:#0a0a0a;box-shadow:3px 3px 0 #0a0a0a;transform:rotate(-2deg)}
.kbn__sr{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;margin:0}
@container (max-width:380px){.kbn__hint{display:none}.kbn__cols{gap:6px}.kbn__col{padding:5px;gap:5px}.kbn__card{padding:4px 6px;line-height:1.25}.kbn__list{gap:4px}}
@media(prefers-reduced-motion:reduce){.kbn__card,.kbn__list{transition:none}.kbn__card.is-grab,.kbn__ghost{transform:none}}// Kanbanbord met slepen · NedDev · eigen werk.
// root = het element waarin de HTML staat
function init(root){
var w=root.querySelector('.kbn');if(!w)return;
var lists=[].slice.call(w.querySelectorAll('.kbn__list')),live=w.querySelector('.kbn__sr[aria-live]'),help=w.querySelector('.kbn__help');
var hid='kbn-help-'+Math.random().toString(36).slice(2,8);help.id=hid;
function cards(l){return [].slice.call(l.querySelectorAll('.kbn__card:not([hidden])'));}
function naam(l){return l.getAttribute('aria-label');}
function count(){lists.forEach(function(l){l.parentNode.querySelector('.kbn__n').textContent=cards(l).length;});}
function say(t){live.textContent=t;}
function pos(c){var l=c.parentNode,a=cards(l);return naam(l)+', plek '+(a.indexOf(c)+1)+' van '+a.length;}
w.querySelectorAll('.kbn__card').forEach(function(c){c.setAttribute('aria-describedby',hid);c.setAttribute('aria-roledescription','kaart');});
var grab=null,orig=null,moving=false;
function place(c,l,i){var a=cards(l).filter(function(x){return x!==c;});moving=true;l.insertBefore(c,a[i]||null);c.focus();moving=false;count();}
function drop(){if(!grab)return;var c=grab;grab=null;c.classList.remove('is-grab');count();say(c.textContent+' neergezet in '+pos(c));}
w.addEventListener('keydown',function(e){var c=e.target.closest&&e.target.closest('.kbn__card');if(!c)return;
var l=c.parentNode,li=lists.indexOf(l),a=cards(l),i=a.indexOf(c),k=e.key;
if(k===' '||k==='Enter'){e.preventDefault();if(grab){drop();}else{grab=c;orig={l:l,n:c.nextElementSibling};c.classList.add('is-grab');say(c.textContent+' opgepakt, '+pos(c)+'. Pijl links of rechts wisselt van kolom, omhoog of omlaag van plek. Spatie zet neer, Escape annuleert.');}return;}
if(k==='Escape'&&grab){e.preventDefault();var g=grab;grab=null;moving=true;orig.l.insertBefore(g,orig.n&&orig.n.parentNode===orig.l?orig.n:null);g.focus();moving=false;g.classList.remove('is-grab');count();say('Geannuleerd. '+g.textContent+' staat weer in '+pos(g));return;}
var d=k==='ArrowLeft'?-1:k==='ArrowRight'?1:0,v=k==='ArrowUp'?-1:k==='ArrowDown'?1:0;if(!d&&!v)return;e.preventDefault();
if(grab===c){if(d){var nl=lists[li+d];if(!nl)return;place(c,nl,Math.min(i,cards(nl).length));}else{var j=i+v;if(j<0||j>=a.length)return;place(c,l,j);}say(pos(c));}
else{if(d){var t=lists[li+d];if(!t)return;var b=cards(t);if(b.length)b[Math.min(i,b.length-1)].focus();}else{var n=a[i+v];if(n)n.focus();}}});
w.addEventListener('focusout',function(){setTimeout(function(){if(grab&&!moving&&document.activeElement!==grab)drop();},0);});
var dr=null;
function overList(x){for(var i=0;i<lists.length;i++){var r=lists[i].parentNode.getBoundingClientRect();if(x>=r.left-4&&x<=r.right+4)return lists[i];}return null;}
w.addEventListener('pointerdown',function(e){var c=e.target.closest('.kbn__card');if(!c||c.classList.contains('kbn__ghost')||e.button!==0||grab)return;dr={c:c,x:e.clientX,y:e.clientY,on:false,id:e.pointerId};try{c.setPointerCapture(e.pointerId);}catch(_){}});
w.addEventListener('pointermove',function(e){if(!dr)return;var c=dr.c;
if(!dr.on){if(Math.abs(e.clientX-dr.x)+Math.abs(e.clientY-dr.y)<6)return;var r=c.getBoundingClientRect();dr.ox=dr.x-r.left;dr.oy=dr.y-r.top;
var g=document.createElement('div');g.className='kbn__card kbn__ghost';g.setAttribute('aria-hidden','true');g.textContent=c.textContent;g.style.width=r.width+'px';w.appendChild(g);dr.g=g;
var ph=document.createElement('li');ph.className='kbn__ph';ph.style.height=r.height+'px';c.parentNode.insertBefore(ph,c);c.hidden=true;dr.ph=ph;dr.on=true;}
var wr=w.getBoundingClientRect(),gx=Math.max(0,Math.min(wr.width-dr.g.offsetWidth,e.clientX-wr.left-dr.ox)),gy=Math.max(0,Math.min(wr.height-dr.g.offsetHeight,e.clientY-wr.top-dr.oy));
dr.g.style.left=gx+'px';dr.g.style.top=gy+'px';
var l=overList(e.clientX);lists.forEach(function(x){x.classList.toggle('is-over',x===l);});if(!l)return;
var a=cards(l),before=null;for(var i=0;i<a.length;i++){var b=a[i].getBoundingClientRect();if(b.top+b.height/2>e.clientY){before=a[i];break;}}
if(before!==dr.ph.nextElementSibling||dr.ph.parentNode!==l)l.insertBefore(dr.ph,before);});
function end(){if(!dr)return;var d=dr;dr=null;if(!d.on)return;d.ph.parentNode.insertBefore(d.c,d.ph);d.ph.remove();d.c.hidden=false;d.g.remove();lists.forEach(function(x){x.classList.remove('is-over');});count();say(d.c.textContent+' verplaatst naar '+pos(d.c));}
w.addEventListener('pointerup',end);w.addEventListener('pointercancel',end);
}
init(document.querySelector('.jouw-container'));
More numbers & charts
Seen something you like?
Add it to your selection and send it over. We build your site around it, in your brand.
Get in touch →