Stappennavigatie voor een wizard New
Step bar for a four-step quote form. Completed steps are buttons that take you back without losing your answers, the current step carries aria-current="step" and upcoming steps stay locked. If a field is wrong the step gets an error mark and focus moves to the field. In a narrow area the bar collapses to the dots with the line "Step 2 of 4: Roof". For quote requests, intakes and checkouts.
How to use it
- Paste the HTML where the navigation & menus 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.
<!-- Stappennavigatie voor een wizard · NedDev · eigen werk. -->
<div class="jouw-container">
<div class="wzs">
<nav class="wzs__nav" aria-label="Stappen offerte zonnepanelen">
<ol class="wzs__steps">
<li class="wzs__li"><button class="wzs__s" type="button" data-i="0"><span class="wzs__dot" aria-hidden="true"></span><span class="wzs__lab">Woning</span></button></li>
<li class="wzs__li"><button class="wzs__s" type="button" data-i="1"><span class="wzs__dot" aria-hidden="true"></span><span class="wzs__lab">Dak</span></button></li>
<li class="wzs__li"><button class="wzs__s" type="button" data-i="2"><span class="wzs__dot" aria-hidden="true"></span><span class="wzs__lab">Verbruik</span></button></li>
<li class="wzs__li"><button class="wzs__s" type="button" data-i="3"><span class="wzs__dot" aria-hidden="true"></span><span class="wzs__lab">Gegevens</span></button></li>
</ol>
<p class="wzs__now" aria-live="polite"></p>
</nav>
<form class="wzs__form" novalidate>
<fieldset class="wzs__pane" data-i="0"><legend class="wzs__lg">Wat voor woning heb je?</legend>
<div class="wzs__opts"><label class="wzs__opt"><input type="radio" name="wzs-w" value="tussenwoning" checked>Tussenwoning</label><label class="wzs__opt"><input type="radio" name="wzs-w" value="hoekwoning">Hoekwoning</label><label class="wzs__opt"><input type="radio" name="wzs-w" value="vrijstaand">Vrijstaand</label></div></fieldset>
<fieldset class="wzs__pane" data-i="1" hidden><legend class="wzs__lg">Welke kant ligt je dak op?</legend>
<label class="wzs__fl" for="wzs-dak">Ligging</label><select class="wzs__in" id="wzs-dak"><option>Zuid</option><option>Oost en west</option><option>Plat dak</option></select></fieldset>
<fieldset class="wzs__pane" data-i="2" hidden><legend class="wzs__lg">Hoeveel stroom gebruik je per jaar?</legend>
<label class="wzs__fl" for="wzs-kwh">Verbruik in kWh</label><input class="wzs__in" id="wzs-kwh" type="number" inputmode="numeric" min="500" max="20000" placeholder="bijvoorbeeld 3500" aria-describedby="wzs-kwh-e"><span class="wzs__err" id="wzs-kwh-e"></span></fieldset>
<fieldset class="wzs__pane" data-i="3" hidden><legend class="wzs__lg">Waar mogen we de offerte op baseren?</legend>
<label class="wzs__fl" for="wzs-pc">Postcode</label><input class="wzs__in" id="wzs-pc" type="text" autocomplete="postal-code" placeholder="1234 AB" aria-describedby="wzs-pc-e"><span class="wzs__err" id="wzs-pc-e"></span></fieldset>
<div class="wzs__pane wzs__done" data-i="4" hidden tabindex="-1"><p class="wzs__lg">Bedankt, je aanvraag is binnen.</p><p class="wzs__fl wzs__sum"></p></div>
<div class="wzs__act"><button class="wzs__btn wzs__prev" type="button">Vorige</button><button class="wzs__btn wzs__next" type="submit">Volgende</button></div>
</form>
</div>
</div>/* Stappennavigatie voor een wizard · NedDev · eigen werk. */
.wzs{position:absolute;inset:0;display:flex;flex-direction:column;padding:12px 16px 12px;background:#fff;color:#0a0a0a;font-family:'Switzer',sans-serif;text-align:left;container-type:inline-size}
.wzs *,.wzs *::before,.wzs *::after{box-sizing:border-box}
.wzs__steps{list-style:none;margin:0;padding:0;display:flex}
.wzs__li{flex:1;position:relative;min-width:0}
.wzs__li+.wzs__li::before{content:'';position:absolute;top:13px;right:calc(50% + 15px);left:calc(-50% + 15px);height:2px;background:#e4e1dc;transition:background-color .3s}
.wzs__li.is-done+.wzs__li::before,.wzs__li.is-done+.wzs__li.is-cur::before{background:#0a0a0a}
.wzs__s{display:flex;flex-direction:column;align-items:center;gap:4px;width:100%;margin:0;padding:0;border:0;background:none;color:#8a857d;font:500 13px/1.1 'Switzer',sans-serif;cursor:default}
.wzs__dot{position:relative;display:grid;place-items:center;width:28px;height:28px;border-radius:50%;border:2px solid #d6d3cd;background:#fff;transition:background-color .25s,border-color .25s}
.wzs__li.is-done .wzs__s{color:#0a0a0a;cursor:pointer}
.wzs__li.is-done .wzs__dot{background:#0a0a0a;border-color:#0a0a0a}
.wzs__li.is-done .wzs__dot::after{content:'';width:9px;height:5px;margin-top:-2px;border:2px solid #fff;border-top:0;border-right:0;transform:rotate(-45deg)}
.wzs__li.is-done .wzs__s:hover .wzs__dot{background:#F5480F;border-color:#F5480F}
.wzs__li.is-cur .wzs__s{color:#0a0a0a;font-weight:600}
.wzs__li.is-cur .wzs__dot{border-color:#F5480F;background:#F5480F;box-shadow:0 0 0 4px #fde3d9}
.wzs__li.is-err .wzs__dot{border-color:#b42318;background:#b42318;box-shadow:0 0 0 4px #fbe0dd}
.wzs__li.is-err .wzs__dot::after{content:'!';color:#fff;font:700 14px/1 'Switzer',sans-serif}
.wzs__s:focus-visible{outline:none}
.wzs__s:focus-visible .wzs__dot{outline:2px solid #F5480F;outline-offset:3px}
.wzs__now{display:none;margin:6px 0 0;font:500 13px/1.2 'Switzer',sans-serif;text-align:center}
.wzs__form{flex:1;min-height:0;display:flex;flex-direction:column;margin-top:10px}
.wzs__pane{flex:1;min-height:0;margin:0;padding:0;border:0;animation:wzsIn .24s ease}
.wzs__pane[hidden]{display:none}
@keyframes wzsIn{from{opacity:0;transform:translateX(var(--dir,12px))}to{opacity:1;transform:none}}
.wzs__lg{margin:0 0 8px;padding:0;font:600 16px/1.25 'Switzer',sans-serif}
.wzs__opts{display:flex;flex-wrap:wrap;gap:6px}
.wzs__opt{position:relative;display:flex;align-items:center;gap:6px;min-height:32px;padding:4px 10px;border:1px solid #d6d3cd;border-radius:4px;font:400 14px/1 'Switzer',sans-serif;cursor:pointer}
.wzs__opt::before{content:'';flex:none;width:12px;height:12px;border-radius:50%;border:2px solid #8a857d;background:#fff;transition:border-color .15s,box-shadow .15s}
.wzs__opt:hover{border-color:#0a0a0a}
.wzs__opt:has(input:checked){border-color:#0a0a0a;background:#f4f3f1;font-weight:500}
.wzs__opt:has(input:checked)::before{border-color:#F5480F;background:#F5480F;box-shadow:inset 0 0 0 2px #fff}
.wzs__opt:has(input:focus-visible){outline:2px solid #F5480F;outline-offset:2px}
.wzs__opt input{position:absolute;width:1px;height:1px;margin:0;opacity:0;overflow:hidden;clip-path:inset(50%)}
.wzs__fl{display:block;margin:0 0 4px;font:400 13px/1.3 'Switzer',sans-serif;color:#57534e}
.wzs__in{width:100%;max-width:240px;height:34px;padding:0 10px;border:1px solid #bdb8b0;border-radius:4px;background:#fff;color:#0a0a0a;font:400 14px/1 'Switzer',sans-serif}
.wzs__in:focus-visible{outline:2px solid #F5480F;outline-offset:1px}
.wzs__in[aria-invalid="true"]{border-color:#b42318}
.wzs__err{display:block;min-height:16px;margin-top:3px;font:500 12px/1.3 'Switzer',sans-serif;color:#b42318}
.wzs__act{display:flex;justify-content:space-between;gap:8px;padding-top:8px}
.wzs__btn{min-height:34px;padding:8px 14px;border-radius:4px;font:600 14px/1 'Switzer',sans-serif;cursor:pointer}
.wzs__prev{border:1px solid #0a0a0a;background:#fff;color:#0a0a0a}
.wzs__prev[hidden]{display:inline-block;visibility:hidden}
.wzs__next{border:1px solid #F5480F;background:#F5480F;color:#0a0a0a}
.wzs__btn:focus-visible{outline:2px solid #0a0a0a;outline-offset:2px}
.wzs__done:focus{outline:none}
@container (max-width:380px){.wzs__lab{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}.wzs__now{display:block}.wzs__dot{width:24px;height:24px}.wzs__li+.wzs__li::before{top:11px;right:calc(50% + 13px);left:calc(-50% + 13px)}.wzs__lg{font-size:15px}.wzs__form{margin-top:6px}.wzs{padding:10px 12px}}
@media (prefers-reduced-motion:reduce){.wzs__pane{animation:none}.wzs__dot,.wzs__li+.wzs__li::before{transition:none}}// Stappennavigatie voor een wizard · NedDev · eigen werk.
// root = het element waarin de HTML staat
function init(root){
var w=root.querySelector('.wzs');if(!w)return;
var lis=[].slice.call(w.querySelectorAll('.wzs__li')),btns=[].slice.call(w.querySelectorAll('.wzs__s')),panes=[].slice.call(w.querySelectorAll('.wzs__pane')),form=w.querySelector('.wzs__form'),prev=w.querySelector('.wzs__prev'),next=w.querySelector('.wzs__next'),now=w.querySelector('.wzs__now');
var names=['Woning','Dak','Verbruik','Gegevens'],cur=0,reached=0;
function render(focusPane){lis.forEach(function(li,i){li.classList.toggle('is-done',i<reached&&i!==cur||i<cur);li.classList.toggle('is-cur',i===cur);
var b=btns[i],open=i<=reached&&i!==cur&&cur<4;b.disabled=false;b.setAttribute('aria-disabled',open?'false':'true');b.tabIndex=open?0:-1;
if(i===cur)b.setAttribute('aria-current','step');else b.removeAttribute('aria-current');
var state=i===cur?'huidige stap':(li.classList.contains('is-err')?'bevat een fout':(li.classList.contains('is-done')?'afgerond':'nog niet bereikbaar'));b.setAttribute('aria-label',names[i]+', '+state);});
panes.forEach(function(p,i){p.hidden=i!==cur;});
now.textContent=cur<4?'Stap '+(cur+1)+' van 4: '+names[cur]:'Alle stappen afgerond';
prev.hidden=cur===0||cur===4;next.hidden=cur===4;next.textContent=cur===3?'Offerte aanvragen':'Volgende';
if(focusPane){var f=panes[cur].querySelector('input:checked,input,select')||panes[cur];f.focus({preventScroll:true});}}
function go(i,dir){w.style.setProperty('--dir',dir<0?'-12px':'12px');cur=i;render(true);}
function check(i){var inp=null,msg='';
if(i===2){inp=w.querySelector('#wzs-kwh');var v=Number(inp.value);if(!inp.value)msg='Vul je jaarverbruik in, het staat op je jaarafrekening.';else if(v<500||v>20000)msg='Kies een getal tussen 500 en 20.000 kWh.';}
if(i===3){inp=w.querySelector('#wzs-pc');var pc=inp.value.trim().toUpperCase().replace(/\s+/g,'');if(!/^[1-9][0-9]{3}[A-Z]{2}$/.test(pc))msg='Vul een postcode in zoals 2611 AB.';else inp.value=pc.slice(0,4)+' '+pc.slice(4);}
if(inp){inp.setAttribute('aria-invalid',msg?'true':'false');w.querySelector('#'+inp.id+'-e').textContent=msg;}
lis[i].classList.toggle('is-err',!!msg);render(false);if(msg){inp.focus();return false;}return true;}
form.addEventListener('submit',function(e){e.preventDefault();if(!check(cur))return;
if(cur===3){var s=w.querySelector('.wzs__sum');s.textContent='We rekenen met '+w.querySelector('#wzs-kwh').value+' kWh per jaar in '+w.querySelector('#wzs-pc').value+'. Je hoort binnen twee werkdagen van ons.';reached=4;go(4,1);return;}
reached=Math.max(reached,cur+1);go(cur+1,1);});
prev.addEventListener('click',function(){if(cur>0)go(cur-1,-1);});
btns.forEach(function(b,i){b.addEventListener('click',function(){if(b.getAttribute('aria-disabled')==='true')return;go(i,i<cur?-1:1);});});
['#wzs-kwh','#wzs-pc'].forEach(function(s){var el=w.querySelector(s);el.addEventListener('input',function(){if(el.getAttribute('aria-invalid')==='true'){el.setAttribute('aria-invalid','false');w.querySelector(s+'-e').textContent='';}});});
render(false);
}
init(document.querySelector('.jouw-container'));
More navigation & menus
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 →