Foutstaat met opnieuw proberen New
Widget that fails gracefully: loading, error with "Try again" (button gets a busy state) and recovery. Plus an offline banner driven by navigator online/offline events that reloads on reconnect; a demo button simulates offline. In production attach your own fetch to load().
Vrije plekken deze week
Fysiotherapie Kralingen
Beschikbaarheid laden...
Laden lukte niet
De agenda reageert even niet. Je keuzes zijn bewaard.
How to use it
- Paste the HTML where the components 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.
<!-- Foutstaat met opnieuw proberen · NedDev · eigen werk. -->
<div class="jouw-container">
<div class="fss">
<div class="fss__bar"><span class="fss__eyebrow">Demo</span><button type="button" class="fss__ctl fss__off" aria-pressed="false">Simuleer offline</button><button type="button" class="fss__ctl fss__restart">Opnieuw starten</button></div>
<div class="fss__banner" role="status" hidden><svg viewBox="0 0 20 20" aria-hidden="true"><path d="M3 7.5a10 10 0 0 1 14 0M5.5 10.5a6.5 6.5 0 0 1 9 0M8.2 13.4a2.6 2.6 0 0 1 3.6 0M3 3l14 14"/></svg><span class="fss__btxt">Je bent offline. We laden opnieuw zodra je weer verbinding hebt.</span></div>
<div class="fss__card">
<div class="fss__head"><p class="fss__title">Vrije plekken deze week</p><p class="fss__sub">Fysiotherapie Kralingen</p></div>
<div class="fss__body" aria-live="polite" aria-busy="true">
<div class="fss__view fss__view--load"><span class="fss__spin" aria-hidden="true"></span><span>Beschikbaarheid laden...</span></div>
<div class="fss__view fss__view--err" hidden><svg class="fss__eico" viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="9"/><path d="M12 7.5v5.5M12 16.2v.3"/></svg><div class="fss__etxt"><p class="fss__eh">Laden lukte niet</p><p class="fss__ep">De agenda reageert even niet. Je keuzes zijn bewaard.</p><button type="button" class="fss__retry"><span class="fss__rspin" aria-hidden="true"></span><span class="fss__rlbl">Opnieuw proberen</span></button></div></div>
<div class="fss__view fss__view--ok" hidden><ul class="fss__slots"><li><button type="button">di 14 okt<b>08:30</b></button></li><li><button type="button">di 14 okt<b>16:45</b></button></li><li><button type="button">do 16 okt<b>10:00</b></button></li><li><button type="button">vr 17 okt<b>13:15</b></button></li></ul></div>
</div>
</div>
</div>
</div>/* Foutstaat met opnieuw proberen · NedDev · eigen werk. */
.fss{position:absolute;inset:0;overflow:auto;display:flex;flex-direction:column;gap:8px;padding:10px 12px 12px;box-sizing:border-box;background:#f4f3f1;color:#0a0a0a;font-family:'Switzer',system-ui,sans-serif;text-align:left}
.fss *,.fss *::before,.fss *::after{box-sizing:border-box}
.fss__bar{display:flex;align-items:center;gap:6px;flex-wrap:wrap}
.fss__eyebrow{font:500 10px/1 'JetBrains Mono',ui-monospace,monospace;letter-spacing:.08em;text-transform:uppercase;color:#5c5a57;margin-right:2px}
.fss__ctl{font:500 11px/1 'JetBrains Mono',ui-monospace,monospace;color:#0a0a0a;background:#fff;border:1px solid #cfccc7;border-radius:999px;padding:6px 10px;cursor:pointer;transition:border-color .15s,background .15s,color .15s}
.fss__ctl:hover{border-color:#0a0a0a}
.fss__ctl[aria-pressed="true"]{background:#0a0a0a;color:#fff;border-color:#0a0a0a}
.fss__ctl:focus-visible,.fss__retry:focus-visible,.fss__slots button:focus-visible{outline:2px solid #F5480F;outline-offset:2px}
.fss__banner{display:flex;align-items:center;gap:8px;padding:8px 10px;border-radius:4px;background:#0a0a0a;color:#fff;font-size:12px;line-height:1.35}
.fss__banner[hidden]{display:none}
.fss__banner.is-back{background:#1d6b3f}
.fss__banner svg{flex:none;width:16px;height:16px;fill:none;stroke:currentColor;stroke-width:1.6;stroke-linecap:round}
.fss__card{flex:1;min-height:120px;background:#fff;border:1px solid #e0ddd8;border-radius:6px;display:flex;flex-direction:column}
.fss__head{padding:10px 12px 8px;border-bottom:1px solid #eeebe7}
.fss__title{margin:0;font-size:14px;font-weight:600}
.fss__sub{margin:2px 0 0;font:500 10px/1.2 'JetBrains Mono',ui-monospace,monospace;color:#5c5a57;letter-spacing:.04em}
.fss__body{flex:1;display:flex;padding:10px 12px}
.fss__view{flex:1;display:flex;align-items:center;gap:10px}
.fss__view[hidden]{display:none}
.fss__view--load{justify-content:center;font-size:13px;color:#5c5a57}
.fss__spin,.fss__rspin{width:16px;height:16px;border:2px solid #e0ddd8;border-top-color:#0a0a0a;border-radius:50%;animation:fss-spin .8s linear infinite}
.fss__view--err{align-items:flex-start}
.fss__eico{flex:none;width:22px;height:22px;fill:none;stroke:#B42318;stroke-width:1.8;stroke-linecap:round}
.fss__eh{margin:1px 0 2px;font-size:14px;font-weight:600}
.fss__ep{margin:0 0 8px;font-size:12.5px;line-height:1.4;color:#5c5a57}
.fss__retry{display:inline-flex;align-items:center;gap:8px;font:600 13px/1 'Switzer',system-ui,sans-serif;padding:9px 13px;border-radius:4px;border:1px solid #0a0a0a;background:#0a0a0a;color:#fff;cursor:pointer;transition:background .15s}
.fss__retry:hover:not(:disabled){background:#2e2c2a}
.fss__retry:active:not(:disabled){transform:translateY(1px)}
.fss__retry:disabled{cursor:progress;background:#3d3b38}
.fss__rspin{display:none;width:13px;height:13px;border-color:rgba(255,255,255,.35);border-top-color:#fff}
.fss__retry[aria-busy="true"] .fss__rspin{display:block}
.fss__slots{list-style:none;margin:0;padding:0;display:grid;grid-template-columns:repeat(auto-fill,minmax(88px,1fr));gap:6px;width:100%;align-self:flex-start}
.fss__slots button{width:100%;display:flex;flex-direction:column;align-items:flex-start;gap:2px;font:500 10px/1.2 'JetBrains Mono',ui-monospace,monospace;color:#5c5a57;background:#fff;border:1px solid #cfccc7;border-radius:4px;padding:7px 9px;cursor:pointer;transition:border-color .15s,box-shadow .15s}
.fss__slots b{font:600 15px/1.1 'Switzer',system-ui,sans-serif;color:#0a0a0a;font-variant-numeric:tabular-nums}
.fss__slots button:hover{border-color:#0a0a0a}
.fss__slots button[aria-pressed="true"]{border-color:#F5480F;box-shadow:inset 0 0 0 1px #F5480F}
.fss__view--ok.is-in{animation:fss-in .3s ease both}
@keyframes fss-spin{to{transform:rotate(360deg)}}
@keyframes fss-in{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:none}}
@media (prefers-reduced-motion:reduce){.fss__spin,.fss__rspin{animation-duration:2.4s}.fss__view--ok.is-in{animation:none}}// Foutstaat met opnieuw proberen · NedDev · eigen werk.
// root = het element waarin de HTML staat
function init(root){
var w=root.querySelector('.fss');if(!w)return;
var body=w.querySelector('.fss__body'),vLoad=w.querySelector('.fss__view--load'),vErr=w.querySelector('.fss__view--err'),vOk=w.querySelector('.fss__view--ok');
var retry=w.querySelector('.fss__retry'),rl=w.querySelector('.fss__rlbl'),eh=w.querySelector('.fss__eh'),ep=w.querySelector('.fss__ep');
var banner=w.querySelector('.fss__banner'),btxt=w.querySelector('.fss__btxt'),offBtn=w.querySelector('.fss__off');
var sim=false,attempt=0,state='load',timer,bt;
function offline(){return sim||navigator.onLine===false;}
function show(s){state=s;vLoad.hidden=s!=='load';vErr.hidden=s!=='err';vOk.hidden=s!=='ok';body.setAttribute('aria-busy',s==='load'?'true':'false');if(s==='ok'){vOk.classList.remove('is-in');void vOk.offsetWidth;vOk.classList.add('is-in');}}
function fail(off){eh.textContent=off?'Geen verbinding':'Laden lukte niet';ep.textContent=off?'Zodra je weer online bent, laden we de agenda vanzelf opnieuw.':'De agenda reageert even niet. Je keuzes zijn bewaard.';show('err');}
function load(fromBtn){clearTimeout(timer);attempt++;
if(fromBtn){retry.disabled=true;retry.setAttribute('aria-busy','true');rl.textContent='Bezig met laden';}else show('load');
timer=setTimeout(function(){retry.disabled=false;retry.removeAttribute('aria-busy');rl.textContent='Opnieuw proberen';
if(offline())fail(true);else if(attempt===1)fail(false);else show('ok');
if(fromBtn&&state==='err')retry.focus();},1100);}
retry.addEventListener('click',function(){load(true);});
w.querySelector('.fss__restart').addEventListener('click',function(){attempt=0;load(false);});
vOk.addEventListener('click',function(e){var b=e.target.closest('button');if(!b)return;vOk.querySelectorAll('button').forEach(function(x){x.setAttribute('aria-pressed',x===b?'true':'false');});});
vOk.querySelectorAll('button').forEach(function(x){x.setAttribute('aria-pressed','false');});
function net(){var off=offline();clearTimeout(bt);
if(off){banner.hidden=false;banner.classList.remove('is-back');btxt.textContent='Je bent offline. We laden opnieuw zodra je weer verbinding hebt.';}
else if(!banner.hidden){banner.classList.add('is-back');btxt.textContent='Weer online. We laden de agenda opnieuw.';bt=setTimeout(function(){banner.hidden=true;},2600);if(state==='err')load(false);}}
offBtn.addEventListener('click',function(){sim=!sim;offBtn.setAttribute('aria-pressed',sim?'true':'false');offBtn.textContent=sim?'Weer online':'Simuleer offline';net();});
window.addEventListener('online',net);window.addEventListener('offline',net);
net();load(false);
}
init(document.querySelector('.jouw-container'));
More components
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 →