Formulierfouten compleet Nieuw
Contactformulier met de volledige foutafhandeling: fout per veld (aria-describedby), foutsamenvatting met links naar de velden, vinkje per geldig veld, typfoutsuggestie voor e-mail, knop die pas actief oogt als alles klopt en een laad- en succesmelding. Gebruikt Constraint-achtige validatie in kleine JS en container queries (fallback: één kolom).
Demo
Bericht verstuurd
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.
<!-- Formulierfouten compleet · NedDev · eigen werk. -->
<div class="jouw-container">
<div class="ffs"><div class="ffs__in">
<div class="ffs__demo" role="group" aria-label="Demo: kies een staat"><span class="ffs__demo-l">Demo</span><button type="button" data-demo="fout">Toon fouten</button><button type="button" data-demo="goed">Vul juist in</button><button type="button" data-demo="leeg">Leeg</button></div>
<form class="ffs__form" novalidate>
<div class="ffs__head"><h3 class="ffs__h">Vraag het Glaszetter Van Dijk</h3><p class="ffs__sub">Velden met * zijn verplicht. Antwoord binnen 1 werkdag.</p></div>
<div class="ffs__sum" tabindex="-1" aria-labelledby="ffs-sumh" hidden><p class="ffs__sum-h" id="ffs-sumh"></p><ul class="ffs__sum-l"></ul></div>
<div class="ffs__grid">
<div class="ffs__f" data-f="naam"><label class="ffs__l" for="ffs-naam">Naam *</label><div class="ffs__ctl"><input class="ffs__i" id="ffs-naam" name="naam" autocomplete="name" required aria-describedby="ffs-naam-e"><svg class="ffs__ok" viewBox="0 0 16 16" aria-hidden="true"><path d="M3 8.5l3 3 7-7"/></svg></div><p class="ffs__e" id="ffs-naam-e"></p></div>
<div class="ffs__f" data-f="email"><label class="ffs__l" for="ffs-email">E-mailadres *</label><div class="ffs__ctl"><input class="ffs__i" id="ffs-email" name="email" type="email" inputmode="email" autocomplete="email" required aria-describedby="ffs-email-e"><svg class="ffs__ok" viewBox="0 0 16 16" aria-hidden="true"><path d="M3 8.5l3 3 7-7"/></svg></div><p class="ffs__e" id="ffs-email-e"></p></div>
<div class="ffs__f" data-f="tel"><label class="ffs__l" for="ffs-tel">Telefoon <small>(optioneel)</small></label><div class="ffs__ctl"><input class="ffs__i" id="ffs-tel" name="tel" type="tel" inputmode="tel" autocomplete="tel" aria-describedby="ffs-tel-e"><svg class="ffs__ok" viewBox="0 0 16 16" aria-hidden="true"><path d="M3 8.5l3 3 7-7"/></svg></div><p class="ffs__e" id="ffs-tel-e"></p></div>
<div class="ffs__f ffs__f--wide" data-f="bericht"><label class="ffs__l" for="ffs-bericht">Waar gaat het om? *</label><div class="ffs__ctl"><textarea class="ffs__i" id="ffs-bericht" name="bericht" rows="3" required aria-describedby="ffs-bericht-e"></textarea><svg class="ffs__ok" viewBox="0 0 16 16" aria-hidden="true"><path d="M3 8.5l3 3 7-7"/></svg></div><p class="ffs__e" id="ffs-bericht-e"></p></div>
</div>
<div class="ffs__act"><button class="ffs__btn" type="submit" aria-disabled="true" aria-describedby="ffs-hint"><span class="ffs__spin" aria-hidden="true"></span><span class="ffs__btn-l">Verstuur bericht</span></button><p class="ffs__hint" id="ffs-hint">Nog 3 velden te gaan</p></div>
</form>
<div class="ffs__done" tabindex="-1" hidden><svg class="ffs__done-i" viewBox="0 0 40 40" aria-hidden="true"><circle cx="20" cy="20" r="18"/><path d="M12 20.5l5.5 5.5L28 15"/></svg><h3 class="ffs__h">Bericht verstuurd</h3><p class="ffs__sub ffs__done-t"></p><button type="button" class="ffs__again">Nog een bericht sturen</button></div>
<p class="ffs__sr" aria-live="polite"></p>
</div></div>
</div>/* Formulierfouten compleet · NedDev · eigen werk. */
.ffs [hidden]{display:none!important}
.ffs{position:absolute;inset:0;overflow:auto;display:flex;padding:14px;box-sizing:border-box;background:#f4f3f1;font-family:'Switzer',system-ui,sans-serif;color:#0a0a0a;container-type:inline-size;scrollbar-width:thin}
.ffs *,.ffs *::before,.ffs *::after{box-sizing:border-box}
.ffs__in{margin:auto;width:100%;max-width:580px}
.ffs__demo{display:flex;flex-wrap:wrap;align-items:center;gap:6px;margin-bottom:10px;font:500 10px 'JetBrains Mono',monospace;letter-spacing:.08em;text-transform:uppercase}
.ffs__demo-l{color:#5a5a5a;margin-right:2px}
.ffs__demo button{font:inherit;letter-spacing:inherit;text-transform:inherit;padding:5px 9px;border:1px solid rgba(10,10,10,.25);background:#fff;color:#0a0a0a;border-radius:100px;cursor:pointer;transition:border-color .15s,background .15s}
.ffs__demo button:hover{border-color:#0a0a0a}
.ffs__form,.ffs__done{background:#fff;border:1px solid rgba(10,10,10,.12);border-radius:6px;padding:16px}
.ffs__head{margin-bottom:12px}
.ffs__h{margin:0;font-size:17px;font-weight:700;letter-spacing:-.01em}
.ffs__sub{margin:4px 0 0;font-size:12.5px;color:#5a5a5a;line-height:1.45}
.ffs__sum{border:1px solid #B42318;border-left-width:4px;background:#fff6f5;border-radius:4px;padding:10px 12px;margin-bottom:12px}
.ffs__sum-h{margin:0 0 4px;font-weight:700;font-size:13.5px;color:#B42318}
.ffs__sum-l{margin:0;padding-left:18px;font-size:13px;line-height:1.55}
.ffs__sum-l a{color:#0a0a0a;text-decoration:underline;text-underline-offset:2px}
.ffs__sum-l a:hover{color:#B42318}
.ffs__grid{display:grid;gap:10px}
@container (min-width:520px){.ffs__grid{grid-template-columns:1fr 1fr}.ffs__f--wide{grid-column:1/-1}}
.ffs__l{display:block;font-size:12.5px;font-weight:600;margin-bottom:4px}
.ffs__l small{font-weight:400;color:#5a5a5a}
.ffs__ctl{position:relative}
.ffs__i{display:block;width:100%;font:400 14px/1.4 'Switzer',system-ui,sans-serif;color:#0a0a0a;background:#fff;border:1px solid #8c8c8c;border-radius:4px;padding:9px 34px 9px 11px;transition:border-color .15s,box-shadow .15s;resize:vertical}
textarea.ffs__i{min-height:64px}
.ffs__i:hover{border-color:#0a0a0a}
.ffs__i:focus-visible{outline:none;border-color:#0a0a0a;box-shadow:0 0 0 3px rgba(245,72,15,.35)}
.ffs__i[readonly]{background:#f7f6f4;color:#5a5a5a}
.ffs__f.is-err .ffs__i{border-color:#B42318;background:#fffafa}
.ffs__f.is-err .ffs__i:focus-visible{box-shadow:0 0 0 3px rgba(180,35,24,.25)}
.ffs__f.is-ok .ffs__i{border-color:#146C43}
.ffs__ok{position:absolute;right:10px;top:11px;width:16px;height:16px;fill:none;stroke:#146C43;stroke-width:2.2;stroke-linecap:round;stroke-linejoin:round;opacity:0;transform:scale(.6);transition:opacity .2s,transform .2s;pointer-events:none}
.ffs__f.is-ok .ffs__ok{opacity:1;transform:none}
.ffs__e{margin:4px 0 0;font-size:12.5px;line-height:1.35;color:#B42318;display:flex;gap:5px;align-items:flex-start}
.ffs__e:empty{display:none}
.ffs__e.is-tip{color:#3d3d3d}
.ffs__e svg{flex:none;width:14px;height:14px;margin-top:1px}
.ffs__fix{font:inherit;color:#0a0a0a;text-decoration:underline;background:none;border:0;padding:0;cursor:pointer;font-weight:700}
.ffs__act{display:flex;flex-wrap:wrap;align-items:center;gap:8px 12px;margin-top:14px}
.ffs__btn{display:inline-flex;align-items:center;gap:8px;font:600 14px 'Switzer',system-ui,sans-serif;padding:10px 18px;border-radius:4px;border:1px solid #0a0a0a;background:#0a0a0a;color:#fff;cursor:pointer;transition:background .15s,border-color .15s,color .15s}
.ffs__btn:hover{background:#F5480F;border-color:#F5480F;color:#0a0a0a}
.ffs__btn:active{transform:translateY(1px)}
.ffs__btn[aria-disabled="true"]{background:#e4e2de;border-color:#e4e2de;color:#4f4f4f;cursor:not-allowed}
.ffs__btn[aria-busy="true"]{background:#0a0a0a;border-color:#0a0a0a;color:#fff;cursor:progress}
.ffs__spin{display:none;width:14px;height:14px;border:2px solid rgba(255,255,255,.35);border-top-color:#fff;border-radius:50%;animation:ffs-spin .7s linear infinite}
.ffs__btn[aria-busy="true"] .ffs__spin{display:inline-block}
@keyframes ffs-spin{to{transform:rotate(360deg)}}
.ffs__hint{margin:0;font-size:12.5px;color:#5a5a5a}
.ffs__done{text-align:center;padding:22px 16px}
.ffs__done:focus{outline:none}
.ffs__done-i{width:44px;height:44px;fill:none;stroke:#146C43;stroke-width:2.4;stroke-linecap:round;stroke-linejoin:round;margin-bottom:6px}
.ffs__done-i path{stroke-dasharray:30;stroke-dashoffset:30;animation:ffs-draw .45s .1s ease-out forwards}
@keyframes ffs-draw{to{stroke-dashoffset:0}}
.ffs__again{margin-top:12px;font:600 13px 'Switzer',system-ui,sans-serif;padding:8px 14px;border:1px solid #0a0a0a;background:#fff;color:#0a0a0a;border-radius:4px;cursor:pointer;transition:background .15s,color .15s}
.ffs__again:hover{background:#0a0a0a;color:#fff}
.ffs__btn:focus-visible,.ffs__again:focus-visible,.ffs__demo button:focus-visible,.ffs__sum-l a:focus-visible,.ffs__fix:focus-visible,.ffs__sum:focus-visible{outline:2px solid #0a0a0a;outline-offset:2px}
.ffs__sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;margin:0}
@media (prefers-reduced-motion:reduce){.ffs *{transition:none!important;animation:none!important}.ffs__done-i path{stroke-dashoffset:0}}// Formulierfouten compleet · NedDev · eigen werk.
// root = het element waarin de HTML staat
function init(root){
root=root.querySelector('.ffs')||root;
(function(r){var s='-'+Math.random().toString(36).slice(2,7);r.querySelectorAll('[id]').forEach(function(el){var o=el.id,n=o+s;el.id=n;['for','aria-describedby','aria-labelledby','aria-controls','popovertarget'].forEach(function(a){r.querySelectorAll('['+a+']').forEach(function(x){x.setAttribute(a,x.getAttribute(a).split(' ').map(function(t){return t===o?n:t;}).join(' '));});});});})(root);
var form=root.querySelector('.ffs__form'),btn=root.querySelector('.ffs__btn'),lbl=root.querySelector('.ffs__btn-l'),hint=root.querySelector('.ffs__hint'),sum=root.querySelector('.ffs__sum'),sumH=root.querySelector('.ffs__sum-h'),sumL=root.querySelector('.ffs__sum-l'),done=root.querySelector('.ffs__done'),doneT=root.querySelector('.ffs__done-t'),sr=root.querySelector('.ffs__sr');
var IC='<svg viewBox="0 0 16 16" aria-hidden="true"><circle cx="8" cy="8" r="7" fill="currentColor"/><path d="M8 4.5v4.2M8 11.2v.3" stroke="#fff" stroke-width="1.8" stroke-linecap="round"/></svg>';
var KEYS=['naam','email','tel','bericht'],F={},busy=false;
KEYS.forEach(function(k){var w=form.querySelector('[data-f="'+k+'"]');F[k]={w:w,i:w.querySelector('.ffs__i'),e:w.querySelector('.ffs__e'),t:false};});
var LABEL={naam:'Naam',email:'E-mailadres',tel:'Telefoon',bericht:'Waar gaat het om?'};
var TYPO={'gmial.com':'gmail.com','gmal.com':'gmail.com','gmail.nl':'gmail.com','hotmial.com':'hotmail.com','hotmal.com':'hotmail.com','hotmial.nl':'hotmail.nl','outlok.com':'outlook.com','ziggo.n':'ziggo.nl','kpnmail.n':'kpnmail.nl'};
var DAG=['zo','ma','di','wo','do','vr','za'],MND=['jan','feb','mrt','apr','mei','jun','jul','aug','sep','okt','nov','dec'];
function check(k){var v=F[k].i.value.trim();
if(k==='naam'){if(!v)return 'Vul je naam in';if(v.length<2)return 'Je naam is te kort';return '';}
if(k==='email'){if(!v)return 'Vul je e-mailadres in';if(!/^[^\s@]+@[^\s@]+\.[a-z]{2,}$/i.test(v))return 'Dit e-mailadres is niet compleet, bijvoorbeeld naam@voorbeeld.nl';return '';}
if(k==='tel'){if(!v)return '';var d=v.replace(/[\s\-().]/g,'').replace(/^\+31/,'0').replace(/^0031/,'0');if(!/^0\d{9}$/.test(d))return 'Een Nederlands nummer heeft 10 cijfers, bijvoorbeeld 06 1234 5678';return '';}
if(!v)return 'Vertel kort waar het om gaat';if(v.length<20)return 'Nog '+(20-v.length)+' tekens, minimaal 20 zodat we je goed kunnen helpen';return '';}
function typo(){var m=F.email.i.value.trim().split('@');var d=m.length===2&&TYPO[m[1].toLowerCase()];return d?m[0]+'@'+d:'';}
function paint(k){var f=F[k],msg=check(k),filled=!!f.i.value.trim();
if(!f.t){f.w.classList.remove('is-err','is-ok');f.i.removeAttribute('aria-invalid');f.e.innerHTML='';f.e.classList.remove('is-tip');return;}
if(msg){f.w.classList.add('is-err');f.w.classList.remove('is-ok');f.i.setAttribute('aria-invalid','true');f.e.classList.remove('is-tip');f.e.innerHTML=IC+'<span>'+msg+'</span>';return;}
f.w.classList.remove('is-err');f.i.removeAttribute('aria-invalid');f.w.classList.toggle('is-ok',filled);
var s=k==='email'&&typo();f.e.classList.toggle('is-tip',!!s);f.e.innerHTML=s?'<span>Bedoel je <button type="button" class="ffs__fix">'+s+'</button>?</span>':'';}
function bad(){return KEYS.filter(function(k){return check(k);});}
function renderSum(b){if(!b.length){sum.hidden=true;return;}sumH.textContent=b.length===1?'Controleer 1 veld':'Controleer '+b.length+' velden';
sumL.innerHTML=b.map(function(k){return '<li><a href="#'+F[k].i.id+'" data-k="'+k+'">'+LABEL[k]+': '+check(k)+'</a></li>';}).join('');}
function upd(){var b=bad();btn.setAttribute('aria-disabled',b.length?'true':'false');hint.textContent=b.length?'Nog '+b.length+(b.length===1?' veld':' velden')+' te gaan':'Klaar om te versturen';if(!sum.hidden)renderSum(b);}
KEYS.forEach(function(k){var f=F[k];
f.i.addEventListener('input',function(){if(f.t)paint(k);upd();});
f.i.addEventListener('blur',function(){if(f.i.value.trim()){f.t=true;paint(k);}upd();});});
form.addEventListener('click',function(e){var x=e.target.closest('.ffs__fix');if(x){F.email.i.value=x.textContent;paint('email');upd();F.email.i.focus();}});
sumL.addEventListener('click',function(e){var a=e.target.closest('a');if(!a)return;e.preventDefault();var i=F[a.dataset.k].i;i.focus();});
function attempt(){var b=bad();if(b.length){KEYS.forEach(function(k){F[k].t=true;paint(k);});renderSum(b);sum.hidden=false;sum.focus();return;}send();}
function nextDay(){var d=new Date();do{d.setDate(d.getDate()+1);}while(d.getDay()===0||d.getDay()===6);return DAG[d.getDay()]+' '+d.getDate()+' '+MND[d.getMonth()];}
function send(){busy=true;sum.hidden=true;btn.setAttribute('aria-busy','true');btn.setAttribute('aria-disabled','true');lbl.textContent='Versturen…';KEYS.forEach(function(k){F[k].i.readOnly=true;});sr.textContent='Je bericht wordt verstuurd';
setTimeout(function(){busy=false;form.hidden=true;done.hidden=false;doneT.textContent='Bedankt, '+F.naam.i.value.trim().split(' ')[0]+'. We reageren uiterlijk '+nextDay()+' via '+F.email.i.value.trim()+'.';sr.textContent='Bericht verstuurd';done.focus();},1200);}
form.addEventListener('submit',function(e){e.preventDefault();if(!busy)attempt();});
function reset(){form.hidden=false;done.hidden=true;btn.removeAttribute('aria-busy');lbl.textContent='Verstuur bericht';KEYS.forEach(function(k){F[k].i.readOnly=false;F[k].i.value='';F[k].t=false;paint(k);});sum.hidden=true;upd();}
root.querySelector('.ffs__again').addEventListener('click',function(){reset();F.naam.i.focus();});
var DEMO={fout:{naam:'',email:'sanne@gmial',tel:'06 1234',bericht:'Lekkage'},goed:{naam:'Sanne de Wit',email:'sanne@voorbeeld.nl',tel:'06 1234 5678',bericht:'Dubbel glas in de achterpui is aan de binnenkant beslagen. Graag een offerte voor vervanging.'}};
root.querySelector('.ffs__demo').addEventListener('click',function(e){var b=e.target.closest('button');if(!b||busy)return;reset();var d=DEMO[b.dataset.demo];if(!d)return;
KEYS.forEach(function(k){F[k].i.value=d[k];});if(b.dataset.demo==='fout')attempt();else{KEYS.forEach(function(k){F[k].t=true;paint(k);});upd();}});
upd();
}
init(document.querySelector('.jouw-container'));
Meer uit formulieren & invoer
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 →