Verticale zijtab die uitschuift New
Two vertical edge tabs ("Quote" and "Feedback") that slide out into a mini form. All states: open/closed, validation errors with aria-invalid, sending with a loading state and a thank-you message in aria-live; the feedback panel asks for a 1 to 10 score. Esc or clicking outside closes and returns focus to the tab.
Offerte aanvragen
Hoe vind je deze pagina?
How to use it
- Paste the HTML where the buttons 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.
<!-- Verticale zijtab die uitschuift · NedDev · eigen werk. -->
<div class="jouw-container">
<div class="ztc"><div class="ztc__pg" aria-hidden="true"><p class="ztc__pk">Dakdekkersbedrijf Mulder · Zwolle</p><p class="ztc__ph1">Lekkage? Binnen 24 uur een dakdekker op je dak.</p><i></i><i></i><i class="is-k"></i></div>
<div class="ztc__dock">
<div class="ztc__tabs"><button class="ztc__tab" type="button" data-p="o" aria-expanded="false">Offerte</button><button class="ztc__tab ztc__tab--f" type="button" data-p="f" aria-expanded="false">Feedback</button></div>
<div class="ztc__pan is-act" data-p="o" role="dialog" aria-label="Offerte aanvragen" inert>
<div class="ztc__hd"><p class="ztc__t">Offerte aanvragen</p><button class="ztc__x" type="button" aria-label="Sluiten"><svg viewBox="0 0 16 16" aria-hidden="true"><path d="M3 3l10 10M13 3L3 13"/></svg></button></div>
<form class="ztc__f" novalidate><label class="ztc__l">Naam<input name="naam" autocomplete="name"></label><span class="ztc__e"></span>
<label class="ztc__l">Telefoon of e-mail<input name="contact" autocomplete="email"></label><span class="ztc__e"></span>
<label class="ztc__l">Waar gaat het om?<select name="soort"><option>Lekkage</option><option>Nieuw plat dak</option><option>Dakkapel</option><option>Dakgoot</option></select></label>
<button class="ztc__send" type="submit"><span>Versturen</span></button></form>
<div class="ztc__ok" role="status" aria-live="polite"></div></div>
<div class="ztc__pan" data-p="f" role="dialog" aria-label="Feedback geven" inert>
<div class="ztc__hd"><p class="ztc__t">Hoe vind je deze pagina?</p><button class="ztc__x" type="button" aria-label="Sluiten"><svg viewBox="0 0 16 16" aria-hidden="true"><path d="M3 3l10 10M13 3L3 13"/></svg></button></div>
<form class="ztc__f" novalidate><fieldset class="ztc__cijfer"><legend>Rapportcijfer</legend>
<label><input type="radio" name="c" value="1"><span>1</span></label><label><input type="radio" name="c" value="2"><span>2</span></label><label><input type="radio" name="c" value="3"><span>3</span></label><label><input type="radio" name="c" value="4"><span>4</span></label><label><input type="radio" name="c" value="5"><span>5</span></label><label><input type="radio" name="c" value="6"><span>6</span></label><label><input type="radio" name="c" value="7"><span>7</span></label><label><input type="radio" name="c" value="8"><span>8</span></label><label><input type="radio" name="c" value="9"><span>9</span></label><label><input type="radio" name="c" value="10"><span>10</span></label></fieldset><span class="ztc__e"></span>
<label class="ztc__l">Wat kan beter? (mag leeg)<textarea name="op" rows="2"></textarea></label>
<button class="ztc__send" type="submit"><span>Verstuur feedback</span></button></form>
<div class="ztc__ok" role="status" aria-live="polite"></div></div>
</div></div>
</div>/* Verticale zijtab die uitschuift · NedDev · eigen werk. */
.ztc{position:absolute;inset:0;overflow:hidden;background:#f4f3f1;color:#0a0a0a;font-family:'Switzer',system-ui,sans-serif;container-type:size}
.ztc__pg{position:absolute;inset:0;padding:16px 44px 16px 16px}
.ztc__pk{margin:0 0 6px;font:500 10px 'JetBrains Mono',monospace;letter-spacing:.06em;text-transform:uppercase;color:#666}
.ztc__ph1{margin:0 0 12px;font:700 clamp(15px,5.4cqi,30px)/1.08 'Switzer',sans-serif;letter-spacing:-.02em;max-width:18ch}
.ztc__pg i{display:block;height:7px;border-radius:2px;background:#e3e0da;margin-bottom:7px;width:72%}
.ztc__pg i+i{width:58%}.ztc__pg i.is-k{width:96px;height:26px;background:#0a0a0a;border-radius:3px;margin-top:12px}
.ztc__dock{--ztc-w:min(250px,68cqi);position:absolute;top:0;right:0;bottom:0;display:flex;align-items:center;transform:translateX(var(--ztc-w));transition:transform .32s cubic-bezier(.6,0,.2,1)}
.ztc.is-open .ztc__dock{transform:none}
.ztc__tabs{display:flex;flex-direction:column;gap:4px}
.ztc__tab{writing-mode:vertical-rl;transform:rotate(180deg);font:600 12px/1 'Switzer',sans-serif;letter-spacing:.02em;padding:14px 8px;border:0;border-radius:0 4px 4px 0;background:#F5480F;color:#fff;cursor:pointer;transition:background .18s,padding .18s}
.ztc__tab--f{background:#0a0a0a}
.ztc__tab:hover{padding-top:18px;padding-bottom:18px}
.ztc__tab:hover{background:#d63c09}.ztc__tab--f:hover{background:#333}
.ztc__tab[aria-expanded="true"]{box-shadow:inset -3px 0 0 #fff}
.ztc__tab:focus-visible{outline:2px solid #0a0a0a;outline-offset:2px}
.ztc__tab--f:focus-visible{outline-color:#F5480F}
.ztc__pan{display:none;width:var(--ztc-w);height:100%;box-sizing:border-box;background:#fff;border-left:1px solid rgba(10,10,10,.12);padding:10px 12px;overflow:auto;box-shadow:-12px 0 30px -18px rgba(0,0,0,.35)}
.ztc__pan.is-act{display:block}
.ztc__hd{display:flex;align-items:center;justify-content:space-between;margin-bottom:6px}
.ztc__t{margin:0;font:700 13px 'Switzer',sans-serif}
.ztc__x{width:26px;height:26px;display:grid;place-items:center;border:0;border-radius:3px;background:transparent;color:#0a0a0a;cursor:pointer}
.ztc__x svg{width:12px;height:12px;stroke:currentColor;stroke-width:1.8;fill:none}
.ztc__x:hover{background:#f4f3f1}
.ztc__x:focus-visible,.ztc__f input:focus-visible,.ztc__f select:focus-visible,.ztc__f textarea:focus-visible,.ztc__send:focus-visible{outline:2px solid #F5480F;outline-offset:1px}
.ztc__f{display:flex;flex-direction:column;gap:3px}
.ztc__f[hidden]{display:none}
.ztc__l{display:flex;flex-direction:column;gap:2px;font:600 10.5px 'Switzer',sans-serif;color:#333}
.ztc__f input:not([type=radio]),.ztc__f select,.ztc__f textarea{font:400 12px 'Switzer',sans-serif;padding:5px 7px;border:1px solid rgba(10,10,10,.28);border-radius:3px;background:#fff;color:#0a0a0a;transition:border-color .18s}
.ztc__f textarea{resize:none;field-sizing:content;min-height:2.6em;max-height:5em}
.ztc__f input:hover,.ztc__f select:hover,.ztc__f textarea:hover{border-color:#0a0a0a}
.ztc__f [aria-invalid="true"]{border-color:#c0281b!important;background:#fff6f5!important}
.ztc__e{font:500 10px/1.3 'Switzer',sans-serif;color:#c0281b;min-height:0}
.ztc__e:empty{display:none}
.ztc__send{margin-top:5px;display:inline-flex;align-items:center;justify-content:center;gap:7px;font:700 12px/1 'Switzer',sans-serif;padding:9px 12px;border-radius:3px;border:0;background:#0a0a0a;color:#fff;cursor:pointer;transition:background .18s}
.ztc__send:hover{background:#F5480F}
.ztc__send:active{transform:translateY(1px)}
.ztc__send[aria-busy="true"]{background:#555;cursor:progress}
.ztc__send[aria-busy="true"]::before{content:'';width:11px;height:11px;border-radius:50%;border:2px solid rgba(255,255,255,.35);border-top-color:#fff;animation:ztc-draai .7s linear infinite}
@keyframes ztc-draai{to{transform:rotate(360deg)}}
.ztc__cijfer{display:grid;grid-template-columns:repeat(5,1fr);gap:3px;border:0;margin:0;padding:0}
.ztc__cijfer legend{font:600 10.5px 'Switzer',sans-serif;color:#333;padding:0;margin-bottom:3px}
.ztc__cijfer label{position:relative}
.ztc__cijfer input{position:absolute;opacity:0;inset:0;margin:0;cursor:pointer}
.ztc__cijfer span{display:block;text-align:center;font:600 11px/1 'JetBrains Mono',monospace;padding:6px 0;border:1px solid rgba(10,10,10,.25);border-radius:3px;transition:background .15s,color .15s}
.ztc__cijfer label:hover span{border-color:#0a0a0a}
.ztc__cijfer input:checked+span{background:#F5480F;border-color:#F5480F;color:#fff}
.ztc__cijfer input:focus-visible+span{outline:2px solid #0a0a0a;outline-offset:1px}
.ztc__cijfer.is-fout span{border-color:#c0281b}
.ztc__ok:empty{display:none}
.ztc__ok{font-size:12.5px;line-height:1.45;padding:10px;border-radius:3px;background:#eef7f1;color:#14532d;border:1px solid #b9dcc6}
.ztc__ok button{margin-top:8px;font:600 11px 'Switzer',sans-serif;border:1px solid #14532d;background:#fff;color:#14532d;border-radius:3px;padding:5px 8px;cursor:pointer}
@media (prefers-reduced-motion: reduce){.ztc__dock{transition:none}.ztc__send[aria-busy="true"]::before{animation:none}}// Verticale zijtab die uitschuift · NedDev · eigen werk.
// root = het element waarin de HTML staat
function init(root){
root=root.querySelector('.ztc')||root;
var tabs=[].slice.call(root.querySelectorAll('.ztc__tab')),pans=[].slice.call(root.querySelectorAll('.ztc__pan')),act=null,uid=Math.random().toString(36).slice(2,7);
pans.forEach(function(p,k){p.id='ztc-'+uid+'-'+k;tabs[k].setAttribute('aria-controls',p.id);p.querySelectorAll('input[type=radio]').forEach(function(r){r.name='ztc-c-'+uid;});
p.querySelectorAll('.ztc__e').forEach(function(e,j){e.id='ztc-e-'+uid+'-'+k+'-'+j;});});
function pan(k){return pans.filter(function(p){return p.getAttribute('data-p')===k;})[0];}
function open(k){act=k;root.classList.add('is-open');pans.forEach(function(p){var on=p.getAttribute('data-p')===k;p.classList.toggle('is-act',on);if(on)p.removeAttribute('inert');else p.setAttribute('inert','');});
tabs.forEach(function(t){t.setAttribute('aria-expanded',t.getAttribute('data-p')===k?'true':'false');});
var f=pan(k).querySelector('input,textarea,select,button.ztc__send');setTimeout(function(){var el=pan(k).querySelector('.ztc__f:not([hidden]) input, .ztc__ok button')||pan(k).querySelector('.ztc__x');if(el)el.focus({preventScroll:true});},60);}
function sluit(){if(!act)return;var k=act;act=null;root.classList.remove('is-open');pans.forEach(function(p){p.setAttribute('inert','');});tabs.forEach(function(t){t.setAttribute('aria-expanded','false');});
var t=tabs.filter(function(t){return t.getAttribute('data-p')===k;})[0];if(t)t.focus({preventScroll:true});}
tabs.forEach(function(t){t.addEventListener('click',function(){var k=t.getAttribute('data-p');if(act===k)sluit();else open(k);});});
root.querySelectorAll('.ztc__x').forEach(function(x){x.addEventListener('click',sluit);});
root.addEventListener('keydown',function(e){if(e.key==='Escape'&&act){e.preventDefault();sluit();}});
root.querySelector('.ztc__pg').addEventListener('pointerdown',function(){sluit();});
function fout(el,err,msg){if(msg){el.setAttribute('aria-invalid','true');el.setAttribute('aria-describedby',err.id);err.textContent=msg;}else{el.removeAttribute('aria-invalid');err.textContent='';}return !!msg;}
pans.forEach(function(p){var f=p.querySelector('.ztc__f'),ok=p.querySelector('.ztc__ok'),knop=f.querySelector('.ztc__send'),errs=f.querySelectorAll('.ztc__e'),soort=p.getAttribute('data-p');
f.addEventListener('submit',function(e){e.preventDefault();if(knop.getAttribute('aria-busy')==='true')return;var eerste=null,naam='';
if(soort==='o'){var n=f.elements.naam,c=f.elements.contact,cv=c.value.trim(),digits=cv.replace(/\D/g,'');naam=n.value.trim();
if(fout(n,errs[0],naam?'':'Vul je naam in.'))eerste=eerste||n;
if(fout(c,errs[1],(cv.indexOf('@')>0||digits.length===10)?'':'Vul een telefoonnummer (10 cijfers) of e-mailadres in.'))eerste=eerste||c;}
else{var gek=f.querySelector('input[type=radio]:checked'),fs=f.querySelector('.ztc__cijfer');fs.classList.toggle('is-fout',!gek);errs[0].textContent=gek?'':'Kies een cijfer van 1 tot 10.';
if(!gek){eerste=f.querySelector('input[type=radio]');fs.setAttribute('aria-describedby',errs[0].id);}naam=gek?gek.value:'';}
if(eerste){eerste.focus();return;}
knop.setAttribute('aria-busy','true');knop.querySelector('span').textContent='Versturen';
setTimeout(function(){knop.removeAttribute('aria-busy');knop.querySelector('span').textContent=soort==='o'?'Versturen':'Verstuur feedback';f.hidden=true;
ok.innerHTML='';var t=document.createElement('p');t.style.margin='0';t.textContent=soort==='o'?('Bedankt, '+naam+'. Jan Mulder belt of mailt je voor morgen 12:00.'):('Dank je! Een '+naam+' noteren we, en we lezen alles.');
var b=document.createElement('button');b.type='button';b.textContent=soort==='o'?'Nog een aanvraag':'Opnieuw invullen';b.addEventListener('click',function(){f.reset();f.hidden=false;ok.innerHTML='';var el=f.querySelector('input');if(el)el.focus();});
ok.appendChild(t);ok.appendChild(b);b.focus({preventScroll:true});},900);});});
}
init(document.querySelector('.jouw-container'));
More buttons
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 →