Balk niet-opgeslagen wijzigingen New
Settings screen where every change slides in a bar "You have 2 unsaved changes" with Cancel and Save (also Ctrl/Cmd+S); changed fields get a dot. Navigating away with open changes asks for confirmation. The browser beforeunload prompt is simulated in the demo; the real line is a comment in the JS.
How to use it
- Paste the HTML where the components 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.
<!-- Balk niet-opgeslagen wijzigingen · NedDev · eigen werk. -->
<div class="jouw-container">
<div class="oob"><div class="oob__app">
<nav class="oob__nav" aria-label="Instellingen"><a href="#" class="oob__nl" aria-current="page">Bedrijfsprofiel</a><a href="#" class="oob__nl" data-leave>Openingstijden</a><a href="#" class="oob__nl" data-leave>Facturen</a><button type="button" class="oob__sim">Tabblad sluiten (demo)</button></nav>
<form class="oob__form" novalidate>
<h3 class="oob__h">Bedrijfsprofiel</h3>
<div class="oob__grid">
<div class="oob__f"><label for="oob-naam">Bedrijfsnaam <i class="oob__dot" aria-hidden="true"></i></label><input class="oob__i" id="oob-naam" name="naam" value="Schildersbedrijf Bakker" aria-describedby="oob-naam-e"><p class="oob__e" id="oob-naam-e"></p></div>
<div class="oob__f"><label for="oob-tel">Telefoon <i class="oob__dot" aria-hidden="true"></i></label><input class="oob__i" id="oob-tel" name="tel" type="tel" value="030 123 4567"></div>
<div class="oob__f oob__f--w"><label for="oob-tijd">Openingstijden ma tot vr <i class="oob__dot" aria-hidden="true"></i></label><input class="oob__i" id="oob-tijd" name="tijd" value="08:00 tot 17:00"></div>
<label class="oob__sw oob__f--w"><input type="checkbox" role="switch" name="wa" checked><span class="oob__tr" aria-hidden="true"></span><span>Toon WhatsApp-knop op de site</span><i class="oob__dot" aria-hidden="true"></i></label>
</div>
<div class="oob__bar" role="region" aria-label="Niet-opgeslagen wijzigingen">
<p class="oob__bt"><span class="oob__bd" aria-hidden="true"></span><span class="oob__n">Je hebt 1 niet-opgeslagen wijziging</span></p>
<div class="oob__ba"><button type="button" class="oob__cancel">Annuleren</button><button type="submit" class="oob__save"><span class="oob__spin" aria-hidden="true"></span>Opslaan</button></div>
</div>
</form>
</div>
<p class="oob__toast" role="status"></p>
<div class="oob__scrim" hidden></div>
<dialog class="oob__dlg" aria-labelledby="oob-dh" aria-describedby="oob-dt"><h4 class="oob__dh" id="oob-dh">Wijzigingen niet opgeslagen</h4><p class="oob__dt" id="oob-dt"></p><div class="oob__da"><button type="button" class="oob__stay">Blijven</button><button type="button" class="oob__leave">Weggaan zonder opslaan</button></div></dialog>
</div>
</div>/* Balk niet-opgeslagen wijzigingen · NedDev · eigen werk. */
.oob [hidden]{display:none!important}
.oob{position:absolute;inset:0;overflow:hidden;background:#f4f3f1;font-family:'Switzer',system-ui,sans-serif;color:#0a0a0a;container-type:inline-size}
.oob *,.oob *::before,.oob *::after{box-sizing:border-box}
.oob__app{position:absolute;inset:0;overflow:auto;padding:12px;display:grid;grid-template-columns:1fr;align-content:start;gap:10px;scrollbar-width:thin}
@container (min-width:560px){.oob__app{grid-template-columns:150px 1fr;max-width:820px;margin:0 auto;padding:18px}}
.oob__nav{display:flex;flex-wrap:wrap;gap:4px;align-items:center}
@container (min-width:560px){.oob__nav{flex-direction:column;align-items:stretch}}
.oob__nl{font-size:13px;font-weight:600;color:#3d3d3d;text-decoration:none;padding:6px 10px;border-radius:4px;transition:background .15s,color .15s}
.oob__nl:hover{background:#e9e6e1;color:#0a0a0a}
.oob__nl[aria-current="page"]{background:#0a0a0a;color:#fff}
.oob__sim{font:500 10px 'JetBrains Mono',monospace;letter-spacing:.04em;text-transform:uppercase;background:none;border:1px dashed rgba(10,10,10,.35);color:#3d3d3d;border-radius:4px;padding:5px 8px;cursor:pointer;margin-left:auto}
@container (min-width:560px){.oob__sim{margin:10px 0 0}}
.oob__sim:hover{border-color:#0a0a0a;color:#0a0a0a}
.oob__form{background:#fff;border:1px solid rgba(10,10,10,.12);border-radius:6px;padding:14px 14px 0;min-width:0}
.oob__h{margin:0 0 10px;font-size:16px;font-weight:700}
.oob__grid{display:grid;gap:10px}
@container (min-width:460px){.oob__grid{grid-template-columns:1fr 1fr}.oob__f--w{grid-column:1/-1}}
.oob__f label{display:flex;align-items:center;gap:6px;font-size:12.5px;font-weight:600;margin-bottom:4px}
.oob__i{display:block;width:100%;font:400 14px 'Switzer',system-ui,sans-serif;color:#0a0a0a;border:1px solid #8c8c8c;border-radius:4px;padding:8px 10px;background:#fff;transition:border-color .15s,box-shadow .15s}
.oob__i:hover{border-color:#0a0a0a}
.oob__i:focus-visible{outline:none;border-color:#0a0a0a;box-shadow:0 0 0 3px rgba(245,72,15,.35)}
.oob__i[aria-invalid="true"]{border-color:#B42318}
.oob__e{margin:4px 0 0;font-size:12px;color:#B42318}.oob__e:empty{display:none}
.oob__dot{display:inline-block;width:7px;height:7px;border-radius:50%;background:#F5480F;opacity:0;transform:scale(.4);transition:opacity .15s,transform .15s}
.is-chg>.oob__dot,.is-chg label .oob__dot{opacity:1;transform:none}
.oob__sw{display:flex;align-items:center;gap:9px;font-size:13.5px;cursor:pointer;position:relative}
.oob__sw input{position:absolute;opacity:0;width:1px;height:1px}
.oob__tr{flex:none;width:34px;height:20px;border-radius:100px;background:#b9b6b0;position:relative;transition:background .15s}
.oob__tr::after{content:'';position:absolute;left:2px;top:2px;width:16px;height:16px;border-radius:50%;background:#fff;transition:transform .18s}
.oob__sw input:checked+.oob__tr{background:#0a0a0a}
.oob__sw input:checked+.oob__tr::after{transform:translateX(14px)}
.oob__sw input:focus-visible+.oob__tr{outline:2px solid #F5480F;outline-offset:2px}
.oob__bar{position:sticky;bottom:0;margin:14px -14px 0;display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:8px 12px;background:#0a0a0a;color:#fff;padding:9px 14px;border-radius:0 0 5px 5px;transform:translateY(100%);opacity:0;visibility:hidden;transition:transform .22s cubic-bezier(.2,.8,.2,1),opacity .22s,visibility 0s .22s}
.oob__bar.is-on{transform:none;opacity:1;visibility:visible;transition:transform .22s cubic-bezier(.2,.8,.2,1),opacity .22s,visibility 0s}
.oob__bt{margin:0;display:flex;align-items:center;gap:8px;font-size:13px;font-weight:600}
.oob__bd{width:8px;height:8px;border-radius:50%;background:#F5480F}
.oob__ba{display:flex;gap:6px}
.oob__cancel,.oob__save,.oob__stay,.oob__leave{font:600 13px 'Switzer',system-ui,sans-serif;border-radius:4px;padding:7px 13px;cursor:pointer;transition:background .15s,color .15s,border-color .15s}
.oob__cancel{background:transparent;border:1px solid rgba(255,255,255,.45);color:#fff}
.oob__cancel:hover{border-color:#fff}
.oob__save{display:inline-flex;align-items:center;gap:7px;background:#F5480F;border:1px solid #F5480F;color:#0a0a0a}
.oob__save:hover{background:#fff;border-color:#fff}
.oob__save[aria-busy="true"]{cursor:progress;background:#fff;border-color:#fff}
.oob__spin{display:none;width:12px;height:12px;border:2px solid rgba(10,10,10,.25);border-top-color:#0a0a0a;border-radius:50%;animation:oob-spin .7s linear infinite}
.oob__save[aria-busy="true"] .oob__spin{display:inline-block}
@keyframes oob-spin{to{transform:rotate(360deg)}}
.oob__cancel:focus-visible,.oob__save:focus-visible{outline:2px solid #fff;outline-offset:2px}
.oob__nl:focus-visible,.oob__sim:focus-visible,.oob__stay:focus-visible,.oob__leave:focus-visible{outline:2px solid #F5480F;outline-offset:2px}
.oob__toast{position:absolute;left:50%;top:10px;z-index:6;margin:0;transform:translate(-50%,-8px);opacity:0;pointer-events:none;background:#146C43;color:#fff;font-size:13px;font-weight:600;padding:7px 14px;border-radius:100px;white-space:nowrap;transition:opacity .2s,transform .2s}
.oob__toast.is-on{opacity:1;transform:translate(-50%,0)}
.oob__scrim{position:absolute;inset:0;z-index:7;background:rgba(10,10,10,.42)}
.oob__dlg{position:absolute;z-index:8;inset:0;margin:auto;height:fit-content;width:min(340px,calc(100% - 24px));border:0;border-radius:6px;padding:16px;background:#fff;color:#0a0a0a;box-shadow:0 18px 50px rgba(10,10,10,.25)}
.oob__dh{margin:0 0 6px;font-size:15.5px}
.oob__dt{margin:0 0 14px;font-size:13px;line-height:1.45;color:#3d3d3d}
.oob__da{display:flex;flex-wrap:wrap;gap:8px;justify-content:flex-end}
.oob__stay{background:#0a0a0a;border:1px solid #0a0a0a;color:#fff}
.oob__stay:hover{background:#F5480F;border-color:#F5480F;color:#0a0a0a}
.oob__leave{background:#fff;border:1px solid #B42318;color:#B42318}
.oob__leave:hover{background:#B42318;color:#fff}
@media (prefers-reduced-motion:reduce){.oob *{transition:none!important}}// Balk niet-opgeslagen wijzigingen · NedDev · eigen werk.
// root = het element waarin de HTML staat
function init(root){
root=root.querySelector('.oob')||root;
// In productie (echte waarschuwing bij tabblad sluiten of herladen):
// window.addEventListener('beforeunload',function(e){if(dirty){e.preventDefault();e.returnValue='';}});
var sfx='-'+Math.random().toString(36).slice(2,7);root.querySelectorAll('[id]').forEach(function(el){var o=el.id,n=o+sfx;el.id=n;['for','aria-describedby','aria-labelledby'].forEach(function(a){root.querySelectorAll('['+a+'="'+o+'"]').forEach(function(x){x.setAttribute(a,n);});});});
var form=root.querySelector('.oob__form'),fields=[].slice.call(form.querySelectorAll('input[name]')),bar=root.querySelector('.oob__bar'),nEl=root.querySelector('.oob__n'),save=root.querySelector('.oob__save'),toast=root.querySelector('.oob__toast'),scrim=root.querySelector('.oob__scrim'),dlg=root.querySelector('.oob__dlg'),dt=root.querySelector('.oob__dt'),stay=root.querySelector('.oob__stay'),leave=root.querySelector('.oob__leave'),naam=form.elements.naam,naamE=root.querySelector('.oob__e');
var base={},dirty=false,busy=false,pending=null,ret=null,tt;
function g(el){return el.type==='checkbox'?el.checked:el.value;}
function snap(){fields.forEach(function(el){base[el.name]=g(el);});}
function setBar(on){bar.classList.toggle('is-on',on);if('inert' in bar)bar.inert=!on;else if(on)bar.removeAttribute('aria-hidden');else bar.setAttribute('aria-hidden','true');}
function diff(){var n=0;fields.forEach(function(el){var c=g(el)!==base[el.name];(el.closest('.oob__f')||el.closest('.oob__sw')).classList.toggle('is-chg',c);if(c)n++;});
var was=dirty;dirty=n>0;setBar(dirty);nEl.textContent='Je hebt '+n+' niet-opgeslagen '+(n===1?'wijziging':'wijzigingen');if(dirty&&!was)say('Je hebt niet-opgeslagen wijzigingen',false);}
function say(m,show){toast.textContent=m;if(show===false){toast.classList.remove('is-on');return;}toast.classList.add('is-on');clearTimeout(tt);tt=setTimeout(function(){toast.classList.remove('is-on');},2200);}
function revert(){fields.forEach(function(el){if(el.type==='checkbox')el.checked=base[el.name];else el.value=base[el.name];});naam.removeAttribute('aria-invalid');naamE.textContent='';diff();}
form.addEventListener('input',diff);form.addEventListener('change',diff);
root.querySelector('.oob__cancel').addEventListener('click',function(){revert();say('Wijzigingen ongedaan gemaakt');naam.focus();});
function doSave(){if(busy||!dirty)return;if(!naam.value.trim()){naam.setAttribute('aria-invalid','true');naamE.textContent='Vul een bedrijfsnaam in';naam.focus();return;}
naam.removeAttribute('aria-invalid');naamE.textContent='';busy=true;save.setAttribute('aria-busy','true');
setTimeout(function(){busy=false;save.removeAttribute('aria-busy');snap();diff();say('Wijzigingen opgeslagen');},800);}
form.addEventListener('submit',function(e){e.preventDefault();doSave();});
root.addEventListener('keydown',function(e){if((e.ctrlKey||e.metaKey)&&(e.key==='s'||e.key==='S')){e.preventDefault();doSave();}});
function openDlg(text,act){pending=act;ret=document.activeElement;dt.textContent=text;scrim.hidden=false;dlg.show();stay.focus();}
function closeDlg(){dlg.close();scrim.hidden=true;if(ret&&ret.focus)ret.focus();}
stay.addEventListener('click',closeDlg);
leave.addEventListener('click',function(){var a=pending;closeDlg();revert();if(a)a();});
scrim.addEventListener('click',closeDlg);
dlg.addEventListener('keydown',function(e){if(e.key==='Escape'){e.preventDefault();closeDlg();}else if(e.key==='Tab'){var f=[stay,leave],i=f.indexOf(document.activeElement);e.preventDefault();f[(i+(e.shiftKey?-1:1)+2)%2].focus();}});
root.querySelectorAll('.oob__nl').forEach(function(a){a.addEventListener('click',function(e){e.preventDefault();if(a.getAttribute('aria-current'))return;
var go=function(){say('Demo: hier opent '+a.textContent);};if(dirty)openDlg('Je hebt wijzigingen in Bedrijfsprofiel die nog niet zijn opgeslagen. Ga je naar '+a.textContent+' zonder op te slaan?',go);else go();});});
root.querySelector('.oob__sim').addEventListener('click',function(){if(dirty)openDlg('Zo ziet de browsermelding eruit: "Deze site verlaten? Wijzigingen die je hebt aangebracht, worden mogelijk niet opgeslagen." De tekst kun je niet aanpassen.',function(){say('Demo: het tabblad zou nu sluiten');});else say('Geen wijzigingen, het tabblad mag dicht');});
snap();diff();
form.elements.tijd.value='08:00 tot 17:30';diff();
}
init(document.querySelector('.jouw-container'));
More components
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 →