Lijst toevoegen en verwijderen Nieuw
Offerteregels toevoegen en verwijderen: een nieuwe regel schuift in, een verwijderde vervaagt en de rest schuift soepel op, het totaal telt mee. View Transitions met view-transition-class voor alle rijen tegelijk; zonder support direct. Toont gevuld, leeg en vol (knop uitgeschakeld). Werkt binnen één pagina, voor multi-page zie de andere vt-items.
Offerte O-2026-0418Badkamer, Delft
Nog geen regels. Voeg een werkzaamheid toe.
Totaal excl. btw
Zo gebruik je het
- Plak de HTML op de plek waar het onderdeel moet komen.
- Zet de CSS in je stylesheet. Alle klassen hebben een eigen voorvoegsel, dus niets botst met je eigen opmaak.
- Voeg de JavaScript toe en roep init() aan met het element waar de HTML in staat.
<!-- Lijst toevoegen en verwijderen · NedDev · eigen werk. -->
<div class="jouw-container">
<!-- Dit is een same-document overgang (geen paginawissel). Elke rij krijgt tijdens de overgang een unieke
view-transition-name (vtl-r1, vtl-r2 ...) en dezelfde view-transition-class: vtl-row, zodat één CSS-regel alle rijen stuurt.
Op een multi-page site werkt hetzelfde over pagina's heen met @view-transition { navigation: auto; } en dezelfde namen op beide pagina's.
Reduced motion: @media (prefers-reduced-motion: reduce) { ::view-transition-group(*), ::view-transition-old(*), ::view-transition-new(*) { animation: none !important; } } -->
<div class="vtl">
<div class="vtl__top"><span class="vtl__kick">Offerte O-2026-0418</span><span class="vtl__sub">Badkamer, Delft</span></div>
<ul class="vtl__list" aria-label="Offerteregels"></ul>
<p class="vtl__leeg" hidden>Nog geen regels. Voeg een werkzaamheid toe.</p>
<div class="vtl__foot">
<button class="vtl__add" type="button"><svg viewBox="0 0 16 16" aria-hidden="true"><path d="M8 3v10M3 8h10"/></svg>Regel toevoegen</button>
<span class="vtl__tot"><small>Totaal excl. btw</small><b></b></span>
</div>
<span class="vtl__sr" aria-live="polite"></span>
</div>
</div>/* Lijst toevoegen en verwijderen · NedDev · eigen werk. */
.vtl{position:absolute;inset:0;display:flex;flex-direction:column;padding:13px 16px 12px;background:#fff;color:#0a0a0a;font-family:'Switzer',system-ui,sans-serif;overflow:hidden}
.vtl__top{display:flex;justify-content:space-between;align-items:baseline;gap:8px;margin-bottom:8px}
.vtl__kick{font:600 10.5px 'JetBrains Mono',monospace;letter-spacing:.08em;text-transform:uppercase}
.vtl__sub{font-size:11.5px;color:rgba(10,10,10,.55)}
.vtl__list{list-style:none;margin:0;padding:0;flex:1;min-height:0;display:flex;flex-direction:column}
.vtl__row{display:flex;align-items:center;gap:10px;padding:6px 0;border-bottom:1px solid rgba(10,10,10,.09);font-size:12.5px;background:#fff}
.vtl__d{flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.vtl__p{font-family:'JetBrains Mono',monospace;font-size:11.5px;font-variant-numeric:tabular-nums;white-space:nowrap}
.vtl__x{all:unset;box-sizing:border-box;width:24px;height:24px;flex:none;border-radius:4px;display:grid;place-items:center;color:rgba(10,10,10,.5);cursor:pointer;transition:background-color .15s,color .15s}
.vtl__x svg{width:12px;height:12px;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round}
.vtl__x:hover{background:#fdece6;color:#b8330a}
.vtl__x:focus-visible,.vtl__add:focus-visible{outline:2px solid #F5480F;outline-offset:2px}
.vtl__leeg{flex:1;margin:0;display:flex;align-items:center;justify-content:center;text-align:center;font-size:12.5px;color:rgba(10,10,10,.55);border:1px dashed rgba(10,10,10,.2);border-radius:4px;padding:10px}
.vtl__leeg[hidden]{display:none}
.vtl__foot{display:flex;justify-content:space-between;align-items:flex-end;gap:10px;padding-top:9px}
.vtl__add{all:unset;box-sizing:border-box;display:inline-flex;align-items:center;gap:6px;font-size:12.5px;font-weight:600;padding:7px 12px 7px 10px;border-radius:4px;background:#0a0a0a;color:#fff;cursor:pointer;transition:background-color .15s}
.vtl__add svg{width:13px;height:13px;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round}
.vtl__add:hover{background:#F5480F}
.vtl__add:active{transform:translateY(1px)}
.vtl__add:disabled{background:rgba(10,10,10,.25);cursor:not-allowed;transform:none}
.vtl__tot{text-align:right;line-height:1.15}
.vtl__tot small{display:block;font-size:10px;color:rgba(10,10,10,.55)}
.vtl__tot b{font-family:'JetBrains Mono',monospace;font-size:15px;font-variant-numeric:tabular-nums}
.vtl__sr{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}
@media (max-width:420px){.vtl{padding:11px 13px 10px}.vtl__row{padding:4px 0;font-size:12px}.vtl__x{width:22px;height:22px}.vtl__tot b{font-size:13.5px}}
/* één regel voor alle rijen dankzij view-transition-class */
::view-transition-group(*.vtl-row){animation-duration:.32s;animation-timing-function:cubic-bezier(.2,.7,.2,1)}
::view-transition-new(*.vtl-row):only-child{animation:vtl-in .32s cubic-bezier(.2,.7,.2,1) both}
::view-transition-old(*.vtl-row):only-child{animation:vtl-uit .2s cubic-bezier(.4,0,1,1) both}
::view-transition-group(vtl-tot){animation-duration:.32s}
@keyframes vtl-in{from{opacity:0;transform:translateX(-14px)}}
@keyframes vtl-uit{to{opacity:0;transform:translateX(14px)}}
@media (prefers-reduced-motion:reduce){::view-transition-group(*.vtl-row),::view-transition-new(*.vtl-row),::view-transition-old(*.vtl-row){animation:none!important}}// Lijst toevoegen en verwijderen · NedDev · eigen werk.
// root = het element waarin de HTML staat
function init(root){
var w=root.querySelector('.vtl');if(!w)return;var ul=w.querySelector('.vtl__list'),leeg=w.querySelector('.vtl__leeg'),add=w.querySelector('.vtl__add'),tot=w.querySelector('.vtl__tot'),totB=tot.querySelector('b'),sr=w.querySelector('.vtl__sr');
var rm=matchMedia('(prefers-reduced-motion: reduce)');var MAX=4,busy=false,k=0;
var POOL=[['Sloopwerk en afvoer',1150],['Tegelwerk wand en vloer',2480],['Inloopdouche met glazen wand',1895],['Vloerverwarming elektrisch',640],['Zwevend toilet met inbouwreservoir',785],['Stucwerk plafond',420]];
var rows=[];
function eur(n){return '€ '+n.toLocaleString('nl-NL',{minimumFractionDigits:2,maximumFractionDigits:2});}
function render(){ul.innerHTML='';rows.forEach(function(r){var li=document.createElement('li');li.className='vtl__row';li.dataset.k=r.k;
li.innerHTML='<span class="vtl__d"></span><span class="vtl__p">'+eur(r.p)+'</span><button class="vtl__x" type="button"><svg viewBox="0 0 16 16" aria-hidden="true"><path d="M4 4l8 8M12 4l-8 8"/></svg></button>';
li.querySelector('.vtl__d').textContent=r.d;li.querySelector('.vtl__x').setAttribute('aria-label','Verwijder '+r.d);ul.appendChild(li);});
leeg.hidden=rows.length>0;ul.hidden=rows.length===0;add.disabled=rows.length>=MAX;add.title=add.disabled?'Maximaal '+MAX+' regels in deze demo':'';
totB.textContent=eur(rows.reduce(function(s,r){return s+r.p;},0));}
function names(on){ul.querySelectorAll('.vtl__row').forEach(function(li){li.style.viewTransitionName=on?'vtl-r'+li.dataset.k:'';li.style.viewTransitionClass=on?'vtl-row':'';});tot.style.viewTransitionName=on?'vtl-tot':'';}
function change(fn,after){if(busy)return;if(!document.startViewTransition||rm.matches){fn();render();after&&after();return;}busy=true;names(true);
var t=document.startViewTransition(function(){fn();render();names(true);});t.finished.finally(function(){names(false);busy=false;after&&after();});}
add.addEventListener('click',function(){if(rows.length>=MAX)return;var used=rows.map(function(r){return r.d;});var nx=POOL.filter(function(p){return used.indexOf(p[0])<0;})[0];if(!nx)return;
change(function(){rows.push({k:++k,d:nx[0],p:nx[1]});},function(){sr.textContent=nx[0]+' toegevoegd. Totaal '+totB.textContent;if(add.disabled){var x=ul.querySelector('.vtl__row:last-child .vtl__x');x&&x.focus({preventScroll:true});}});});
ul.addEventListener('click',function(e){var b=e.target.closest('.vtl__x');if(!b)return;var li=b.closest('.vtl__row'),key=+li.dataset.k,idx=rows.findIndex(function(r){return r.k===key;}),d=rows[idx].d;
change(function(){rows.splice(idx,1);},function(){sr.textContent=d+' verwijderd. Totaal '+totB.textContent;var nxt=ul.querySelectorAll('.vtl__x')[Math.min(idx,rows.length-1)];(nxt||add).focus({preventScroll:true});});});
[0,1,2].forEach(function(i){rows.push({k:++k,d:POOL[i][0],p:POOL[i][1]});});render();
}
init(document.querySelector('.jouw-container'));
Meer uit componenten
Iets gezien dat je mooi vindt?
Zet het in je selectie en stuur hem op. Dan bouwen we je site eromheen, in jouw huisstijl.
Neem contact op →