Stappennavigatie voor een wizard Nieuw
Stappenbalk voor een offerteformulier in vier stappen. Afgeronde stappen zijn knoppen waarmee je terug kunt zonder dat je antwoorden verdwijnen, de huidige stap draagt aria-current="step" en komende stappen zijn nog dicht. Klopt een veld niet, dan krijgt die stap een foutmarkering en gaat de focus naar het veld. Is het vlak smal, dan klapt de balk in tot de stippen met de regel "Stap 2 van 4: Dak". Voor offerte-aanvragen, intakes en bestelstraten.
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.
<!-- 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'));
Meer uit navigatie & menu's
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 →