Toasts: succes, fout, voortgang, ongedaan maken Nieuw
Vier soorten toasts op één offertescherm: succes (verdwijnt na 5 s), fout (blijft staan, met "Opnieuw" en laadstaat), voortgang met balk die eindigt in "Download" en verwijderen met "Ongedaan maken" en een aftelbalk. Ze stapelen als een kaartspel en vouwen open bij hover of focus; timers pauzeren dan en ook als het tabblad weg is. Aankondiging via aria-live (fout assertief). In-animatie met @starting-style.
Offerte 2026-041Concept
Keukenrenovatie fam. Bakker, Zeist
€ 18.450,00 incl. btw
Offerte 2026-041 is verwijderd.
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.
<!-- Toasts: succes, fout, voortgang, ongedaan maken · NedDev · eigen werk. -->
<div class="jouw-container">
<div class="tsv">
<div class="tsv__card">
<div class="tsv__row"><span class="tsv__eyebrow">Offerte 2026-041</span><span class="tsv__state">Concept</span></div>
<strong class="tsv__title">Keukenrenovatie fam. Bakker, Zeist</strong>
<span class="tsv__amt">€ 18.450,00 incl. btw</span>
<div class="tsv__btns"><button type="button" data-t="ok">Opslaan</button><button type="button" data-t="err">Versturen</button><button type="button" data-t="prog">PDF maken</button><button type="button" data-t="undo" class="tsv__del">Verwijderen</button></div>
</div>
<div class="tsv__gone"><p>Offerte 2026-041 is verwijderd.</p><button type="button" class="tsv__restore">Zet voorbeeld terug</button></div>
<ol class="tsv__region" aria-label="Meldingen"></ol>
<div class="tsv__sr" aria-live="polite"></div><div class="tsv__sr" aria-live="assertive"></div>
</div>
</div>/* Toasts: succes, fout, voortgang, ongedaan maken · NedDev · eigen werk. */
.tsv{--ink:#0a0a0a;--pop:#F5480F;--pd:#b83a0b;--line:rgba(10,10,10,.12);position:absolute;inset:0;overflow:hidden;font-family:'Switzer',system-ui,sans-serif;color:var(--ink);padding:14px 16px}
.tsv__card{background:#fff;border:1px solid var(--line);border-radius:6px;padding:12px 14px;display:flex;flex-direction:column;gap:4px;max-width:340px}
.tsv__row{display:flex;justify-content:space-between;align-items:center}
.tsv__eyebrow{font:600 9.5px/1 'JetBrains Mono',monospace;letter-spacing:.12em;text-transform:uppercase;color:#5b5b5b}
.tsv__state{font:600 9.5px/1 'JetBrains Mono',monospace;letter-spacing:.08em;text-transform:uppercase;padding:4px 6px;border-radius:3px;background:#f4f3f1}
.tsv__title{font-size:14px}
.tsv__amt{font-size:12px;color:#5b5b5b;font-variant-numeric:tabular-nums}
.tsv__btns{display:flex;flex-wrap:wrap;gap:6px;margin-top:8px}
.tsv__btns button,.tsv__restore{font:600 11.5px/1 'Switzer',system-ui,sans-serif;padding:8px 10px;border:1px solid var(--ink);border-radius:4px;background:#fff;color:var(--ink);cursor:pointer;transition:background .15s,color .15s}
.tsv__btns button:hover,.tsv__restore:hover{background:var(--ink);color:#fff}
.tsv__btns button:active{transform:translateY(1px)}
.tsv__btns .tsv__del{border-color:#b3261e;color:#b3261e}
.tsv__btns .tsv__del:hover{background:#b3261e;color:#fff}
.tsv button:focus-visible{outline:2px solid var(--pd);outline-offset:2px}
.tsv__gone{display:none;max-width:340px;padding:16px;border:1px dashed rgba(10,10,10,.3);border-radius:6px;font-size:13px;color:#5b5b5b}
.tsv__gone p{margin:0 0 10px}
.tsv.is-deleted .tsv__card{display:none}.tsv.is-deleted .tsv__gone{display:block}
.tsv__region{position:absolute;right:12px;bottom:12px;width:min(290px,calc(100% - 24px));height:var(--fh,0);margin:0;padding:0;list-style:none;z-index:5}
.tsv__t{position:absolute;left:0;right:0;bottom:0;display:grid;grid-template-columns:18px 1fr auto auto;align-items:center;gap:8px;padding:9px 6px 9px 11px;border-radius:6px;background:var(--ink);color:#fff;box-shadow:0 10px 26px rgba(10,10,10,.22);transform-origin:50% 100%;overflow:hidden;opacity:1;translate:0 0;transition:transform .28s cubic-bezier(.2,.8,.2,1),opacity .25s ease,translate .3s cubic-bezier(.2,.8,.2,1)}
@starting-style{.tsv__t{translate:0 110%;opacity:0}}
.tsv__t.is-back{height:var(--fh)}
.tsv__t.is-back>*{opacity:0}
.tsv__t.is-out{opacity:0;translate:24px 0}
.tsv__ic{width:18px;height:18px;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}
.tsv__t--ok .tsv__ic{stroke:#6fd39a}.tsv__t--err .tsv__ic{stroke:#ff8a80}.tsv__t--prog .tsv__ic,.tsv__t--undo .tsv__ic{stroke:#ffb08f}
.tsv__msg{min-width:0;font-size:12.5px;line-height:1.3}
.tsv__msg b,.tsv__msg span{display:block;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.tsv__msg b{font-weight:600}
.tsv__msg span{color:#c9c9c9;font-size:11.5px}
.tsv__a{font:700 11.5px/1 'Switzer',system-ui,sans-serif;padding:7px 8px;border:0;border-radius:4px;background:rgba(255,255,255,.12);color:#fff;cursor:pointer;white-space:nowrap;transition:background .15s}
.tsv__a:hover{background:rgba(255,255,255,.24)}
.tsv__a[aria-busy=true]::before{content:'';display:inline-block;vertical-align:-1px;width:9px;height:9px;margin-right:6px;border:2px solid #fff;border-right-color:transparent;border-radius:50%;animation:tsv-spin .7s linear infinite}
@keyframes tsv-spin{to{transform:rotate(360deg)}}
.tsv__x{width:26px;height:26px;border:0;border-radius:4px;background:none;color:#c9c9c9;cursor:pointer;display:grid;place-items:center;padding:0}
.tsv__x:hover{color:#fff;background:rgba(255,255,255,.12)}
.tsv__x svg{width:13px;height:13px;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round}
.tsv__t .tsv__a:focus-visible,.tsv__t .tsv__x:focus-visible{outline:2px solid var(--pop);outline-offset:1px}
.tsv__bar{position:absolute;left:0;bottom:0;height:3px;width:100%;background:var(--pop);transform-origin:left;transform:scaleX(0)}
.tsv__t--undo .tsv__bar{transform:scaleX(1);animation:tsv-cd var(--d,6s) linear forwards}
.tsv__region:hover .tsv__t--undo .tsv__bar,.tsv__region:focus-within .tsv__t--undo .tsv__bar,.tsv.is-hidden .tsv__t--undo .tsv__bar{animation-play-state:paused}
@keyframes tsv-cd{to{transform:scaleX(0)}}
.tsv__sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
@media (max-width:420px){.tsv{padding:10px 12px}.tsv__card{padding:10px 12px}.tsv__btns{margin-top:4px}.tsv__btns button{padding:7px 8px;font-size:11px}}
@media (prefers-reduced-motion:reduce){.tsv__t{transition:none}}// Toasts: succes, fout, voortgang, ongedaan maken · NedDev · eigen werk.
// root = het element waarin de HTML staat
function init(root){
var w=root.querySelector('.tsv');if(!w)return;
var reg=w.querySelector('.tsv__region'),srs=w.querySelectorAll('.tsv__sr'),list=[],MAX=4;
var IC={ok:'<circle cx="12" cy="12" r="9"/><path d="m8 12 3 3 5-6"/>',err:'<circle cx="12" cy="12" r="9"/><path d="M9 9l6 6M15 9l-6 6"/>',prog:'<path d="M12 3v12M7 10l5 5 5-5M5 20h14"/>',undo:'<path d="M9 14 4 9l5-5"/><path d="M4 9h10a6 6 0 0 1 0 12h-3"/>'};
var X='<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M6 6l12 12M18 6 6 18"/></svg>';
function now(){var d=new Date();return d.getHours()+':'+(d.getMinutes()<10?'0':'')+d.getMinutes();}
function say(txt,assert){var el=srs[assert?1:0];el.textContent='';setTimeout(function(){el.textContent=txt;},40);}
function expanded(){return reg.matches(':hover')||reg.contains(document.activeElement);}
function layout(){var exp=expanded(),y=0,front=list[0];if(front)front.el.classList.remove('is-back');reg.style.setProperty('--fh',front?front.el.offsetHeight+'px':'0px');
list.forEach(function(t,i){t.el.classList.toggle('is-back',!exp&&i>0);t.el.style.zIndex=20-i;
if(exp){t.el.style.transform='translateY('+(-y)+'px)';y+=t.el.offsetHeight+8;t.el.style.opacity='';}
else{t.el.style.transform='translateY('+(-i*9)+'px) scale('+(1-i*.05)+')';t.el.style.opacity=i>2?'0':'';}});}
function paused(){return expanded()||document.hidden;}
function arm(t){clearTimeout(t.tm);if(!t.dur||paused())return;t.t0=Date.now();t.tm=setTimeout(function(){if(t.onEnd)t.onEnd();remove(t);},t.left);}
function hold(){list.forEach(function(t){if(t.tm){clearTimeout(t.tm);t.tm=0;t.left=Math.max(400,t.left-(Date.now()-t.t0));}});}
function resume(){list.forEach(function(t){if(!t.tm)arm(t);});}
function remove(t){clearTimeout(t.tm);var i=list.indexOf(t);if(i<0)return;list.splice(i,1);t.el.classList.add('is-out');var had=t.el.contains(document.activeElement);setTimeout(function(){t.el.remove();},260);layout();
if(had){var n=list[0],f=n?n.el.querySelector('.tsv__x'):(w.classList.contains('is-deleted')?w.querySelector('.tsv__restore'):w.querySelector('.tsv__btns button'));if(f)f.focus();}}
function set(t,kind,title,sub,action,dur){t.el.className='tsv__t tsv__t--'+kind;t.el.innerHTML='<svg class="tsv__ic" viewBox="0 0 24 24" aria-hidden="true">'+IC[kind]+'</svg><div class="tsv__msg"><b>'+title+'</b><span>'+sub+'</span></div>'+(action?'<button class="tsv__a" type="button">'+action+'</button>':'<span></span>')+'<button class="tsv__x" type="button" aria-label="Melding sluiten">'+X+'</button>'+(kind==='prog'||kind==='undo'?'<span class="tsv__bar" aria-hidden="true"></span>':'');
t.el.querySelector('.tsv__x').addEventListener('click',function(){remove(t);});t.dur=dur||0;t.left=dur||0;if(kind==='undo')t.el.style.setProperty('--d',dur+'ms');arm(t);layout();
say(title+'. '+sub,kind==='err');return t.el.querySelector('.tsv__a');}
function push(){var li=document.createElement('li');var t={el:li};reg.insertBefore(li,reg.firstChild);list.unshift(t);while(list.length>MAX)remove(list[list.length-1]);return t;}
var make={
ok:function(){var t=push();set(t,'ok','Opgeslagen','Concept bijgewerkt om '+now(),'',5000);},
err:function(){var t=push();var a=set(t,'err','Versturen mislukt','Geen e-mailadres bij de klant','Opnieuw',0);
a.addEventListener('click',function(){a.setAttribute('aria-busy','true');a.textContent='Bezig';setTimeout(function(){if(list.indexOf(t)<0)return;set(t,'ok','Offerte verstuurd','Naar bakker@voorbeeld.nl','',5000);var x=t.el.querySelector('.tsv__x');if(reg.contains(document.activeElement)||document.activeElement===document.body)x.focus();},1200);});},
prog:function(){var t=push();set(t,'prog','PDF wordt gemaakt','0%','',0);var bar=t.el.querySelector('.tsv__bar'),sub=t.el.querySelector('.tsv__msg span'),p=0;
var iv=setInterval(function(){if(list.indexOf(t)<0){clearInterval(iv);return;}p=Math.min(100,p+7+Math.random()*9);bar.style.transform='scaleX('+p/100+')';sub.textContent=Math.round(p)+'% · 4 blz.';
if(p>=100){clearInterval(iv);var a=set(t,'ok','PDF klaar','offerte-2026-041.pdf · 212 kB','Download',5000);a.addEventListener('click',function(){remove(t);});}},180);},
undo:function(){w.classList.add('is-deleted');var t=push();var a=set(t,'undo','Offerte verwijderd','2026-041 · Keukenrenovatie','Ongedaan maken',6000);
a.addEventListener('click',function(){t.onEnd=null;w.classList.remove('is-deleted');set(t,'ok','Offerte teruggezet','Alles staat weer zoals het was','',4000);w.querySelector('.tsv__del').focus();});
t.onEnd=function(){if(t.el.contains(document.activeElement))w.querySelector('.tsv__restore').focus();};
if(w.contains(document.activeElement)||document.activeElement===document.body)a.focus();}
};
w.querySelectorAll('.tsv__btns button').forEach(function(b){b.addEventListener('click',function(){make[b.dataset.t]();});});
w.querySelector('.tsv__restore').addEventListener('click',function(){w.classList.remove('is-deleted');w.querySelector('.tsv__btns button').focus();});
reg.addEventListener('mouseenter',function(){hold();layout();});reg.addEventListener('mouseleave',function(){setTimeout(function(){layout();if(!paused())resume();},0);});
reg.addEventListener('focusin',function(){hold();layout();});reg.addEventListener('focusout',function(){setTimeout(function(){layout();if(!paused())resume();},0);});
document.addEventListener('visibilitychange',function(){w.classList.toggle('is-hidden',document.hidden);if(document.hidden)hold();else resume();});
}
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 →