Knop wordt bevestigingskaart New
The "Cancel appointment" button grows into a confirmation card (native dialog) and shrinks back; with loading, error (tick "storing") and success. View Transitions for the shape change, without support or with reduced motion the card appears instantly. Esc and Back close it, focus returns to the button. For a separate confirmation page on a multi-page site: @view-transition { navigation: auto; } and the same view-transition-name on button and card.
Garage Bakker · ZwolleGepland
APK en kleine beurt
GX-418-K
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.
<!-- Knop wordt bevestigingskaart · NedDev · eigen werk. -->
<div class="jouw-container">
<!-- Knop en dialog krijgen om beurten view-transition-name: vtd-shape; de browser morpht de vorm.
De dialog opent met show() (niet-modaal, blijft in het blok). Op een echte pagina mag showModal() en is Esc ingebouwd.
Bevestiging als aparte pagina (multi-page): @view-transition { navigation: auto; } en op beide pagina's view-transition-name: vtd-shape
op de knop en op de kaart. Reduced motion: @media (prefers-reduced-motion: reduce) { ::view-transition-group(*), ::view-transition-old(*), ::view-transition-new(*) { animation: none !important; } } -->
<div class="vtd">
<div class="vtd__card">
<div class="vtd__row"><span class="vtd__kick">Garage Bakker · Zwolle</span><span class="vtd__state">Gepland</span></div>
<p class="vtd__h">APK en kleine beurt</p>
<p class="vtd__meta">vr 16 okt · 09:30 tot ca. 11:00</p>
<div class="vtd__plate" aria-label="Kenteken GX-418-K"><span class="vtd__nl" aria-hidden="true">NL</span><span>GX-418-K</span></div>
<button class="vtd__open" type="button" aria-haspopup="dialog">Afspraak annuleren</button>
<button class="vtd__nieuw" type="button" hidden>Nieuwe afspraak plannen</button>
<dialog class="vtd__dlg" aria-labelledby="vtd-t" aria-describedby="vtd-d">
<p class="vtd__dt" id="vtd-t">Afspraak annuleren?</p>
<p class="vtd__dd" id="vtd-d">Je APK op vr 16 okt om 09:30 vervalt. Kosteloos tot 24 uur vooraf.</p>
<p class="vtd__err" role="alert" hidden></p>
<div class="vtd__acts"><button class="vtd__terug" type="button">Terug</button><button class="vtd__ja" type="button">Ja, annuleren</button></div>
</dialog>
</div>
<label class="vtd__sim"><input type="checkbox" class="vtd__storing"> storing</label>
<span class="vtd__sr" aria-live="polite"></span>
</div>
</div>/* Knop wordt bevestigingskaart · NedDev · eigen werk. */
.vtd{position:absolute;inset:0;background:#f4f3f1;font-family:'Switzer',system-ui,sans-serif;color:#0a0a0a;display:flex;align-items:center;justify-content:center;padding:12px}
.vtd__card{position:relative;width:100%;max-width:370px;height:100%;max-height:210px;background:#fff;border-radius:6px;box-shadow:0 0 0 1px rgba(10,10,10,.08),0 6px 18px rgba(10,10,10,.06);padding:13px 15px;box-sizing:border-box}
.vtd__row{display:flex;justify-content:space-between;align-items:center;gap:8px}
.vtd__kick{font:500 10px 'JetBrains Mono',monospace;letter-spacing:.1em;text-transform:uppercase;color:rgba(10,10,10,.6)}
.vtd__state{font:600 10px 'JetBrains Mono',monospace;padding:2px 7px;border-radius:999px;background:#e8f4ec;color:#1b6b3c}
.vtd__state.is-off{background:#fdece6;color:#a32d08}
.vtd__h{margin:8px 0 2px;font-size:17px;font-weight:700;letter-spacing:-.01em}
.vtd__meta{margin:0 0 9px;font-size:12px;color:rgba(10,10,10,.65)}
.vtd__plate{display:inline-flex;align-items:stretch;border:1.5px solid #0a0a0a;border-radius:3px;overflow:hidden;font:700 13px 'JetBrains Mono',monospace;letter-spacing:.04em;background:#f7c600}
.vtd__plate span:last-child{padding:3px 8px}
.vtd__nl{background:#1d3e9e;color:#fff;font-size:8px;padding:0 4px;display:flex;align-items:flex-end;padding-bottom:3px}
.vtd__open,.vtd__nieuw{all:unset;box-sizing:border-box;position:absolute;right:13px;bottom:13px;font-size:12.5px;font-weight:600;padding:8px 13px;border-radius:4px;cursor:pointer;transition:background-color .15s,color .15s,border-color .15s}
.vtd__open{border:1px solid rgba(10,10,10,.25);color:#a32d08;background:#fff}
.vtd__open:hover{border-color:#a32d08;background:#fdece6}
.vtd__nieuw{background:#0a0a0a;color:#fff}
.vtd__nieuw:hover{background:#F5480F}
.vtd__open[hidden],.vtd__nieuw[hidden]{display:none}
.vtd__open:focus-visible,.vtd__nieuw:focus-visible,.vtd__terug:focus-visible,.vtd__ja:focus-visible{outline:2px solid #F5480F;outline-offset:2px}
.vtd__open:active,.vtd__nieuw:active,.vtd__ja:active,.vtd__terug:active{transform:translateY(1px)}
.vtd__dlg{position:absolute;inset:auto 8px 8px auto;margin:0;width:min(268px,calc(100% - 16px));box-sizing:border-box;padding:12px 13px;border:0;border-radius:6px;background:#0a0a0a;color:#fff;box-shadow:0 10px 30px rgba(10,10,10,.28)}
.vtd__dt{margin:0 0 4px;font-size:14px;font-weight:700}
.vtd__dd{margin:0 0 10px;font-size:11.5px;line-height:1.45;color:rgba(255,255,255,.72)}
.vtd__err{margin:-4px 0 9px;font-size:11.5px;line-height:1.4;color:#ffb199}
.vtd__err[hidden]{display:none}
.vtd__acts{display:flex;justify-content:flex-end;gap:6px}
.vtd__terug,.vtd__ja{all:unset;box-sizing:border-box;font-size:12px;font-weight:600;padding:7px 11px;border-radius:4px;cursor:pointer;transition:background-color .15s,color .15s}
.vtd__terug{color:#fff;box-shadow:inset 0 0 0 1px rgba(255,255,255,.3)}
.vtd__terug:hover{background:rgba(255,255,255,.1)}
.vtd__ja{background:#F5480F;color:#0a0a0a}
.vtd__ja:hover{background:#ff6a36}
.vtd__ja[aria-busy="true"]{cursor:progress;display:inline-flex;align-items:center;gap:6px;opacity:.85}
.vtd__ja[aria-busy="true"]::before{content:'';width:10px;height:10px;border-radius:50%;border:2px solid currentColor;border-right-color:transparent;animation:vtd-spin .7s linear infinite}
@keyframes vtd-spin{to{transform:rotate(360deg)}}
.vtd__sim{position:absolute;left:14px;bottom:6px;display:flex;align-items:center;gap:4px;font:500 9.5px 'JetBrains Mono',monospace;color:rgba(10,10,10,.5);cursor:pointer}
.vtd__sim input{margin:0;width:11px;height:11px;accent-color:#F5480F}
.vtd__sr{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}
@media (max-width:420px){.vtd{padding:8px 8px 18px}.vtd__card{padding:10px 12px}.vtd__h{font-size:15px;margin-top:6px}.vtd__meta{margin-bottom:7px}.vtd__open,.vtd__nieuw{right:10px;bottom:10px;padding:7px 11px;font-size:12px}.vtd__dd{font-size:11px;margin-bottom:8px}.vtd__dlg{padding:10px 11px}.vtd__sim{bottom:3px}}
@media (prefers-reduced-motion:reduce){.vtd__ja[aria-busy="true"]::before{animation-duration:2s}}
::view-transition-group(vtd-shape){animation-duration:.36s;animation-timing-function:cubic-bezier(.2,.8,.2,1);overflow:clip;border-radius:6px}
::view-transition-old(vtd-shape),::view-transition-new(vtd-shape){height:100%;width:100%;object-fit:none;object-position:right bottom}
::view-transition-old(vtd-shape){animation:vtd-uit .14s ease-out both}
::view-transition-new(vtd-shape){animation:vtd-in .22s ease-out .08s both}
@keyframes vtd-uit{to{opacity:0}}
@keyframes vtd-in{from{opacity:0}}
@media (prefers-reduced-motion:reduce){::view-transition-group(vtd-shape),::view-transition-old(vtd-shape),::view-transition-new(vtd-shape){animation:none!important}}// Knop wordt bevestigingskaart · NedDev · eigen werk.
// root = het element waarin de HTML staat
function init(root){
var w=root.querySelector('.vtd');if(!w)return;var openB=w.querySelector('.vtd__open'),nieuw=w.querySelector('.vtd__nieuw'),dlg=w.querySelector('.vtd__dlg'),terug=w.querySelector('.vtd__terug'),ja=w.querySelector('.vtd__ja'),err=w.querySelector('.vtd__err'),stor=w.querySelector('.vtd__storing'),state=w.querySelector('.vtd__state'),sr=w.querySelector('.vtd__sr');
var rm=matchMedia('(prefers-reduced-motion: reduce)');var busy=false;
var uid='vtd'+Math.random().toString(36).slice(2,7);var t1=w.querySelector('#vtd-t'),d1=w.querySelector('#vtd-d');t1.id=uid+'-t';d1.id=uid+'-d';dlg.setAttribute('aria-labelledby',t1.id);dlg.setAttribute('aria-describedby',d1.id);
function morph(from,to,upd,after){if(!document.startViewTransition||rm.matches){upd();after&&after();return;}busy=true;from.style.viewTransitionName='vtd-shape';
var t=document.startViewTransition(function(){from.style.viewTransitionName='';upd();to.style.viewTransitionName='vtd-shape';});
t.finished.finally(function(){to.style.viewTransitionName='';busy=false;after&&after();});}
function openD(){if(busy||dlg.open)return;err.hidden=true;ja.textContent='Ja, annuleren';
morph(openB,dlg,function(){openB.hidden=true;dlg.show();},function(){terug.focus({preventScroll:true});});}
function closeD(toBtn){if(busy||!dlg.open)return;morph(dlg,toBtn,function(){dlg.close();toBtn.hidden=false;},function(){toBtn.focus({preventScroll:true});});}
openB.addEventListener('click',openD);terug.addEventListener('click',function(){closeD(openB);});
dlg.addEventListener('keydown',function(e){if(e.key==='Escape'){e.preventDefault();if(ja.getAttribute('aria-busy')!=='true')closeD(openB);}
else if(e.key==='Tab'){var f=[terug,ja];var i=f.indexOf(document.activeElement);if(e.shiftKey&&i<=0){e.preventDefault();ja.focus();}else if(!e.shiftKey&&i===f.length-1){e.preventDefault();terug.focus();}}});
dlg.addEventListener('cancel',function(e){e.preventDefault();});
ja.addEventListener('click',function(){if(ja.getAttribute('aria-busy')==='true')return;ja.setAttribute('aria-busy','true');ja.textContent='Bezig';terug.disabled=true;err.hidden=true;
setTimeout(function(){ja.removeAttribute('aria-busy');terug.disabled=false;
if(stor.checked){ja.textContent='Opnieuw proberen';err.textContent='Dat lukte niet. Probeer het opnieuw of bel 038 123 4567.';err.hidden=false;ja.focus({preventScroll:true});return;}
state.textContent='Geannuleerd';state.classList.add('is-off');sr.textContent='Afspraak geannuleerd. Je krijgt een bevestiging per mail.';closeD(nieuw);},900);});
nieuw.addEventListener('click',function(){state.textContent='Gepland';state.classList.remove('is-off');nieuw.hidden=true;openB.hidden=false;openB.focus({preventScroll:true});sr.textContent='Nieuwe afspraak gepland voor vr 16 okt, 09:30';});
}
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 →