Verzendknop met alle staten New
Submit button that stays disabled until the e-mail is valid, then loads (spinner, aria-busy) and ends in success (tick) or error (brief shake, cross, explanation) before returning to normal. Pick whether the next try succeeds; the strip below shows every state side by side. CSS keyframes, no shake with reduced motion.
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.
<!-- Verzendknop met alle staten · NedDev · eigen werk. -->
<div class="jouw-container">
<div class="skf"><div class="skf__in">
<form class="skf__form" novalidate>
<label class="skf__l" for="skf-m">Nieuwsbrief van Bakkerij Verhoef</label>
<div class="skf__row"><input class="skf__i" id="skf-m" type="email" autocomplete="email" placeholder="naam@voorbeeld.nl" aria-describedby="skf-h"><button class="skf__btn" type="submit" data-s="idle" disabled aria-describedby="skf-h"><span class="skf__ic" aria-hidden="true"><span class="skf__spin"></span><svg class="skf__ok" viewBox="0 0 16 16"><path d="M3 8.5l3 3 7-7"/></svg><svg class="skf__no" viewBox="0 0 16 16"><path d="M4 4l8 8M12 4l-8 8"/></svg></span><span class="skf__t">Aanmelden</span></button></div>
<p class="skf__h" id="skf-h">Vul eerst een geldig e-mailadres in.</p>
<div class="skf__sim"><span id="skf-sl">Volgende poging:</span><span class="skf__seg" role="radiogroup" aria-labelledby="skf-sl"><label><input type="radio" name="skf-r" value="ok" checked><span>slaagt</span></label><label><input type="radio" name="skf-r" value="fout"><span>mislukt</span></label></span></div>
</form>
<div class="skf__all" aria-hidden="true">
<span class="skf__mini"><span class="skf__btn skf__btn--m" data-s="idle"><span class="skf__t">Aanmelden</span></span><small>standaard</small></span>
<span class="skf__mini"><span class="skf__btn skf__btn--m is-off" data-s="idle"><span class="skf__t">Aanmelden</span></span><small>uit</small></span>
<span class="skf__mini"><span class="skf__btn skf__btn--m" data-s="laden"><span class="skf__ic"><span class="skf__spin"></span></span><span class="skf__t">Bezig</span></span><small>laden</small></span>
<span class="skf__mini"><span class="skf__btn skf__btn--m" data-s="ok"><span class="skf__ic"><svg class="skf__ok" viewBox="0 0 16 16"><path d="M3 8.5l3 3 7-7"/></svg></span><span class="skf__t">Gelukt</span></span><small>succes</small></span>
<span class="skf__mini"><span class="skf__btn skf__btn--m" data-s="fout"><span class="skf__ic"><svg class="skf__no" viewBox="0 0 16 16"><path d="M4 4l8 8M12 4l-8 8"/></svg></span><span class="skf__t">Mislukt</span></span><small>fout</small></span>
</div>
<p class="skf__live" aria-live="polite"></p>
</div></div>
</div>/* Verzendknop met alle staten · NedDev · eigen werk. */
.skf [hidden]{display:none!important}
.skf{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;scrollbar-width:thin}
.skf *,.skf *::before,.skf *::after{box-sizing:border-box}
.skf__in{margin:auto;width:100%;max-width:440px}
.skf__form{background:#fff;border:1px solid rgba(10,10,10,.12);border-radius:6px;padding:14px}
.skf__l{display:block;font-size:13.5px;font-weight:700;margin-bottom:6px}
.skf__row{display:flex;gap:8px}
.skf__i{flex:1;min-width:0;font:400 14px 'Switzer',system-ui,sans-serif;color:#0a0a0a;border:1px solid #8c8c8c;border-radius:4px;padding:9px 11px;background:#fff;transition:border-color .15s,box-shadow .15s}
.skf__i:hover{border-color:#0a0a0a}
.skf__i:focus-visible{outline:none;border-color:#0a0a0a;box-shadow:0 0 0 3px rgba(245,72,15,.35)}
.skf__btn{flex:none;display:inline-flex;align-items:center;justify-content:center;gap:7px;min-width:122px;font:600 14px 'Switzer',system-ui,sans-serif;padding:9px 14px;border-radius:4px;border:1px solid #0a0a0a;background:#0a0a0a;color:#fff;cursor:pointer;transition:background .18s,border-color .18s,color .18s}
button.skf__btn:hover:not(:disabled){background:#F5480F;border-color:#F5480F;color:#0a0a0a}
button.skf__btn:active:not(:disabled){transform:translateY(1px)}
.skf__btn:focus-visible{outline:2px solid #F5480F;outline-offset:2px}
.skf__btn:disabled[data-s="idle"],.skf__btn.is-off{background:#e4e2de;border-color:#e4e2de;color:#5a5a5a;cursor:not-allowed}
.skf__btn[data-s="laden"]{background:#2b2b2b;border-color:#2b2b2b;color:#fff;cursor:progress}
.skf__btn[data-s="ok"]{background:#146C43;border-color:#146C43;color:#fff}
.skf__btn[data-s="fout"]{background:#B42318;border-color:#B42318;color:#fff}
.skf__form .skf__btn[data-s="fout"]{animation:skf-shake .36s cubic-bezier(.36,.07,.19,.97)}
@keyframes skf-shake{20%,60%{transform:translateX(-4px)}40%,80%{transform:translateX(4px)}}
.skf__ic{display:none;width:15px;height:15px;position:relative}
.skf__btn[data-s="laden"] .skf__ic,.skf__btn[data-s="ok"] .skf__ic,.skf__btn[data-s="fout"] .skf__ic{display:inline-block}
.skf__spin,.skf__ok,.skf__no{position:absolute;inset:0;display:none}
.skf__spin{border:2px solid rgba(255,255,255,.35);border-top-color:#fff;border-radius:50%;animation:skf-spin .7s linear infinite}
.skf__ok,.skf__no{width:15px;height:15px;fill:none;stroke:#fff;stroke-width:2.3;stroke-linecap:round;stroke-linejoin:round}
.skf__btn[data-s="laden"] .skf__spin,.skf__btn[data-s="ok"] .skf__ok,.skf__btn[data-s="fout"] .skf__no{display:block}
@keyframes skf-spin{to{transform:rotate(360deg)}}
.skf__h{margin:6px 0 0;font-size:12.5px;color:#5a5a5a;line-height:1.35}
.skf__h.is-err{color:#B42318}.skf__h.is-ok{color:#146C43}
.skf__sim{display:flex;flex-wrap:wrap;align-items:center;gap:6px;margin-top:10px;font:500 10.5px 'JetBrains Mono',monospace;letter-spacing:.04em;color:#5a5a5a}
.skf__seg{display:inline-flex;border:1px solid rgba(10,10,10,.25);border-radius:100px;overflow:hidden}
.skf__seg label{position:relative;cursor:pointer}
.skf__seg input{position:absolute;opacity:0;width:1px;height:1px}
.skf__seg span{display:block;padding:4px 10px;color:#0a0a0a;transition:background .15s,color .15s}
.skf__seg input:checked+span{background:#0a0a0a;color:#fff}
.skf__seg input:focus-visible+span{outline:2px solid #F5480F;outline-offset:-2px}
.skf__all{display:flex;flex-wrap:wrap;gap:8px 4px;margin-top:10px;justify-content:center}
.skf__mini{display:flex;flex-direction:column;align-items:center;gap:3px}
.skf__btn--m{min-width:0;font-size:11px;padding:5px 7px;cursor:default;gap:5px}
.skf__btn--m .skf__ic{width:11px;height:11px}.skf__btn--m .skf__ok,.skf__btn--m .skf__no{width:11px;height:11px}
.skf__mini small{font:500 9.5px 'JetBrains Mono',monospace;letter-spacing:.06em;text-transform:uppercase;color:#5a5a5a}
.skf__live{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;margin:0}
@media (prefers-reduced-motion:reduce){.skf *{transition:none!important}.skf__form .skf__btn[data-s="fout"]{animation:none}.skf__spin{animation-duration:1.6s}}// Verzendknop met alle staten · NedDev · eigen werk.
// root = het element waarin de HTML staat
function init(root){
root=root.querySelector('.skf')||root;
var sfx='-'+Math.random().toString(36).slice(2,7);['skf-m','skf-h','skf-sl'].forEach(function(o){var el=root.querySelector('#'+o);if(el)el.id=o+sfx;});
var form=root.querySelector('.skf__form'),inp=root.querySelector('.skf__i'),btn=form.querySelector('.skf__btn'),t=btn.querySelector('.skf__t'),h=root.querySelector('.skf__h'),live=root.querySelector('.skf__live'),busy=false,timer;
root.querySelector('.skf__l').setAttribute('for',inp.id);inp.setAttribute('aria-describedby',h.id);btn.setAttribute('aria-describedby',h.id);root.querySelector('.skf__seg').setAttribute('aria-labelledby',root.querySelector('[id^="skf-sl"]').id);
root.querySelectorAll('.skf__seg input').forEach(function(r){r.name='skf-r'+sfx;});
function ok(){return /^[^\s@]+@[^\s@]+\.[a-z]{2,}$/i.test(inp.value.trim());}
function idle(){busy=false;btn.dataset.s='idle';btn.removeAttribute('aria-busy');t.textContent='Aanmelden';inp.readOnly=false;sync();}
function sync(){if(busy)return;btn.disabled=!ok();h.className='skf__h';h.textContent=ok()?'Je kunt je aanmelden. Eén mail per maand, opzeggen kan altijd.':'Vul eerst een geldig e-mailadres in.';}
inp.addEventListener('input',sync);
form.addEventListener('submit',function(e){e.preventDefault();if(busy||btn.disabled)return;busy=true;clearTimeout(timer);
btn.dataset.s='laden';btn.setAttribute('aria-busy','true');t.textContent='Bezig';inp.readOnly=true;live.textContent='Bezig met aanmelden';
var slaagt=form.querySelector('.skf__seg input:checked').value==='ok';
setTimeout(function(){btn.removeAttribute('aria-busy');
if(slaagt){btn.dataset.s='ok';t.textContent='Gelukt';h.className='skf__h is-ok';h.textContent='Aangemeld. Check je inbox om te bevestigen.';live.textContent='Aangemeld. Check je inbox om te bevestigen.';timer=setTimeout(function(){inp.value='';idle();},2600);}
else{btn.dataset.s='fout';t.textContent='Mislukt';h.className='skf__h is-err';h.textContent='Aanmelden lukte niet aan onze kant. Probeer het over een paar seconden opnieuw.';live.textContent='Aanmelden mislukt. Probeer het zo opnieuw.';timer=setTimeout(function(){idle();btn.focus();},2600);}},1100);});
inp.value='';sync();
}
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 →