Abonnement opzeggen New
Cancelling a subscription the way Dutch rules expect: the cancel button is as visible as change, then one confirmation screen with end date and last direct debit, a reason that is truly optional, no retention tricks, and a confirmation showing the confirmation email text and a reference number. Loading and error (via the demo switch) are visible. Switches screens with View Transitions; instantly without support or with reduced motion.
Mijn abonnement
Flex-abonnement, Sportschool De Kade Utrecht
Opzegging bevestigen
Je Flex-abonnement loopt door tot en met (opzegtermijn 1 maand). De laatste incasso is .
Opzegging ontvangen
- Einddatum
- Ontvangen op
- Referentie
Bevestigingsmail naar s.devries@voorbeeld.nl
Onderwerp: Bevestiging van je opzegging
Hoi Sanne, we hebben je opzegging van het Flex-abonnement ontvangen op . Je kunt nog sporten tot en met . De laatste incasso is ; daarna schrijven we niets meer af. Bewaar deze mail als bewijs. Referentie: .
How to use it
- Paste the HTML where the forms & inputs 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.
<!-- Abonnement opzeggen · NedDev · eigen werk. -->
<div class="jouw-container">
<div class="ozf">
<div class="ozf__v" data-v="1">
<h3 class="ozf__h" tabindex="-1">Mijn abonnement</h3>
<div class="ozf__sub">
<p class="ozf__name">Flex-abonnement, Sportschool De Kade Utrecht</p>
<p class="ozf__meta">€ 34,95 per maand, maandelijks opzegbaar. Volgende incasso op 1 november.</p>
<div class="ozf__row">
<button type="button" class="ozf__btn" data-go="wijzig">Abonnement wijzigen</button>
<button type="button" class="ozf__btn" data-go="2">Abonnement opzeggen</button>
</div>
<p class="ozf__note" aria-live="polite"></p>
</div>
</div>
<div class="ozf__v" data-v="2" hidden>
<h3 class="ozf__h" tabindex="-1">Opzegging bevestigen</h3>
<p class="ozf__p">Je Flex-abonnement loopt door tot en met <strong class="ozf__end"></strong> (opzegtermijn 1 maand). De laatste incasso is <span class="ozf__last"></span>.</p>
<fieldset class="ozf__fs">
<legend>Waarom stop je? <span>(optioneel, je mag dit overslaan)</span></legend>
<label><input type="radio" name="ozf-r" value="verhuizing"> Ik ga verhuizen</label>
<label><input type="radio" name="ozf-r" value="prijs"> Te duur</label>
<label><input type="radio" name="ozf-r" value="weinig"> Ik sport te weinig</label>
<label><input type="radio" name="ozf-r" value="anders"> Anders</label>
<textarea class="ozf__ta" rows="2" aria-label="Toelichting (optioneel)" placeholder="Toelichting (optioneel)" hidden></textarea>
</fieldset>
<p class="ozf__err" role="alert"></p>
<div class="ozf__row">
<button type="button" class="ozf__btn ozf__btn--main" data-go="3">Opzegging bevestigen</button>
<button type="button" class="ozf__btn" data-go="1">Toch niet</button>
</div>
<label class="ozf__demo"><input type="checkbox"> Demo: simuleer een storing</label>
</div>
<div class="ozf__v" data-v="3" hidden>
<h3 class="ozf__h" tabindex="-1">Opzegging ontvangen</h3>
<dl class="ozf__dl">
<dt>Einddatum</dt><dd class="ozf__end"></dd>
<dt>Ontvangen op</dt><dd class="ozf__got"></dd>
<dt>Referentie</dt><dd class="ozf__ref"></dd>
</dl>
<div class="ozf__mail">
<p class="ozf__mh">Bevestigingsmail naar s.devries@voorbeeld.nl</p>
<p><strong>Onderwerp:</strong> Bevestiging van je opzegging</p>
<p>Hoi Sanne, we hebben je opzegging van het Flex-abonnement ontvangen op <span class="ozf__got"></span>. Je kunt nog sporten tot en met <span class="ozf__end"></span>. De laatste incasso is <span class="ozf__last"></span>; daarna schrijven we niets meer af. Bewaar deze mail als bewijs. Referentie: <span class="ozf__ref"></span>.</p>
</div>
<div class="ozf__row"><button type="button" class="ozf__btn" data-go="undo">Opzegging intrekken</button></div>
</div>
</div>
</div>/* Abonnement opzeggen · NedDev · eigen werk. */
.ozf{--ink:#0a0a0a;--pop:#F5480F;--pd:#b83a0b;--mut:#5b5b5b;--line:rgba(10,10,10,.12);position:absolute;inset:0;overflow:auto;padding:12px 14px;font-family:'Switzer',system-ui,sans-serif;font-size:12.5px;line-height:1.45;color:var(--ink);background:#fff;scrollbar-width:thin;text-align:left}
.ozf *,.ozf *::before,.ozf *::after{box-sizing:border-box}
.ozf__v[hidden]{display:none}
.ozf__h{margin:0 0 8px;font-size:15px;font-weight:600;letter-spacing:-.01em}
.ozf__h:focus{outline:none}
.ozf__sub{padding:12px;border:1px solid var(--line);border-radius:4px;background:#f4f3f1}
.ozf__name{margin:0;font-weight:600;font-size:13px}
.ozf__meta{margin:2px 0 10px;color:var(--mut)}
.ozf__note{margin:8px 0 0;font-size:12px}
.ozf__note:empty{display:none}
.ozf__p{margin:0 0 8px}
.ozf__row{display:flex;flex-wrap:wrap;gap:6px}
.ozf__btn{height:32px;padding:0 12px;border:1px solid var(--ink);border-radius:4px;background:#fff;color:var(--ink);font:500 12.5px/1 'Switzer',system-ui,sans-serif;cursor:pointer;transition:background-color .15s,color .15s}
.ozf__btn:hover{background:var(--ink);color:#fff}
.ozf__btn:active{transform:translateY(1px)}
.ozf__btn--main{background:var(--ink);color:#fff}
.ozf__btn--main:hover{background:#2a2a2a}
.ozf__btn[aria-busy="true"]{opacity:.6;cursor:progress}
.ozf button:focus-visible,.ozf input:focus-visible,.ozf textarea:focus-visible{outline:2px solid var(--pop);outline-offset:2px}
.ozf__fs{margin:0 0 8px;padding:8px 10px;border:1px solid var(--line);border-radius:4px;display:flex;flex-wrap:wrap;gap:4px 14px}
.ozf__fs legend{padding:0 4px;font-weight:600;font-size:12px}
.ozf__fs legend span{font-weight:400;color:var(--mut)}
.ozf__fs label{display:inline-flex;align-items:center;gap:5px;cursor:pointer}
.ozf__fs input{accent-color:var(--ink);margin:0}
.ozf__ta{flex-basis:100%;width:100%;padding:6px 8px;border:1px solid rgba(10,10,10,.3);border-radius:4px;font:500 12.5px/1.35 'Switzer',system-ui,sans-serif;resize:vertical}
@supports (field-sizing:content){.ozf__ta{field-sizing:content;min-height:44px;max-height:110px}}
.ozf__ta[hidden]{display:none}
.ozf__err{margin:0 0 8px;color:var(--pd);font-weight:500}
.ozf__err:empty{display:none}
.ozf__demo{display:flex;align-items:center;gap:6px;margin-top:10px;padding-top:8px;border-top:1px dashed var(--line);font-size:11.5px;color:var(--mut);cursor:pointer}
.ozf__demo input{accent-color:var(--ink);margin:0}
.ozf__dl{display:grid;grid-template-columns:auto 1fr;gap:2px 12px;margin:0 0 10px}
.ozf__dl dt{color:var(--mut)}
.ozf__dl dd{margin:0;font-weight:600}
.ozf__mail{margin:0 0 10px;padding:10px 12px;border:1px solid var(--line);border-radius:4px;background:#f4f3f1;font-size:12px}
.ozf__mail p{margin:0 0 4px}
.ozf__mh{color:var(--mut)}
@media (prefers-reduced-motion:reduce){.ozf *{transition:none!important}}// Abonnement opzeggen · NedDev · eigen werk.
// root = het element waarin de HTML staat
function init(root){
const w = root.querySelector('.ozf'); if (!w) return;
const reduce = matchMedia('(prefers-reduced-motion: reduce)').matches;
const fD = new Intl.DateTimeFormat('nl-NL', { day: 'numeric', month: 'long', year: 'numeric' });
const fT = new Intl.DateTimeFormat('nl-NL', { weekday: 'short', day: 'numeric', month: 'short', hour: '2-digit', minute: '2-digit' });
const eur = new Intl.NumberFormat('nl-NL', { style: 'currency', currency: 'EUR' });
const ta = w.querySelector('.ozf__ta'), err = w.querySelector('.ozf__err'), demo = w.querySelector('.ozf__demo input');
const note = w.querySelector('.ozf__note');
let cancelled = false;
function dates() {
const now = new Date(), end = new Date(now); end.setMonth(end.getMonth() + 1);
const first = new Date(end.getFullYear(), end.getMonth(), 1);
const dim = new Date(end.getFullYear(), end.getMonth() + 1, 0).getDate();
const amt = Math.round(34.95 * end.getDate() / dim * 100) / 100;
return { now, end, last: 'op ' + fD.format(first) + ' (' + eur.format(amt) + ', naar rato)' };
}
const set = (sel, t) => w.querySelectorAll(sel).forEach(e => { e.textContent = t; });
function go(n) {
const swap = () => {
w.querySelectorAll('.ozf__v').forEach(v => { v.hidden = v.dataset.v !== String(n); });
w.scrollTop = 0;
w.querySelector('.ozf__v[data-v="' + n + '"] .ozf__h').focus();
};
if (document.startViewTransition && !reduce) document.startViewTransition(swap); else swap();
}
const d0 = dates(); set('.ozf__end', fD.format(d0.end)); set('.ozf__last', d0.last);
w.addEventListener('change', e => {
if (e.target.name === 'ozf-r') { ta.hidden = e.target.value !== 'anders'; if (!ta.hidden) ta.focus(); }
});
w.addEventListener('click', e => {
const b = e.target.closest('[data-go]'); if (!b) return;
const g = b.dataset.go;
if (g === 'wijzig') { note.textContent = 'Wijzigen opent in de echte omgeving de keuze tussen Flex, Basis en Duo.'; return; }
if (g === '1') { err.textContent = ''; go(1); if (cancelled) note.textContent = ''; return; }
if (g === '2') { note.textContent = ''; err.textContent = ''; go(2); return; }
if (g === 'undo') { cancelled = false; go(1); setTimeout(() => { note.textContent = 'Opzegging ingetrokken. Je abonnement loopt gewoon door.'; }, 60); return; }
if (g === '3') {
if (b.getAttribute('aria-busy') === 'true') return;
err.textContent = ''; b.setAttribute('aria-busy', 'true'); const label = b.textContent; b.textContent = 'Bezig met opzeggen...';
setTimeout(() => {
b.removeAttribute('aria-busy'); b.textContent = label;
if (demo.checked) { err.textContent = 'Opzeggen lukte niet door een storing bij ons. Er is niets gewijzigd. Probeer het zo opnieuw, of bel 030 123 4567.'; b.focus(); return; }
const d = dates(); cancelled = true;
set('.ozf__end', fD.format(d.end)); set('.ozf__last', d.last);
set('.ozf__got', fT.format(d.now)); set('.ozf__ref', 'OPZ-' + d.now.getFullYear() + '-' + String(48213 + d.now.getMinutes()).padStart(5, '0'));
go(3);
}, 800);
}
});
}
init(document.querySelector('.jouw-container'));
More forms & inputs
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 →