Rits die een actie onthult New
A closed jacket panel with a zip: pull the tab down with mouse or finger and both halves part in a V; past halfway the zip opens fully by itself and the offer underneath appears, letting go earlier springs it shut. The tab is a slider: arrow keys open it step by step, Enter or End in one go. For the homepage or newsletter landing of a clothing shop, for a winter sale or a new collection.
Winterjassen 30% korting
Alleen dit weekend, in de winkel aan de Herestraat in Groningen en online.
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.
<!-- Rits die een actie onthult · © 2026 NedDev BV · NedDev Lab-licentie (gratis gebruik, niet doorverkopen) · neddev.com/nl/lab/gebruik/#licentie -->
<div class="jouw-container">
<div class="rto">
<div class="rto__binnen" inert>
<p class="rto__kop">Winterjassen 30% korting</p>
<p class="rto__sub">Alleen dit weekend, in de winkel aan de Herestraat in Groningen en online.</p>
<div class="rto__rij"><a class="rto__knop" href="#">Bekijk de jassen</a><button type="button" class="rto__dicht">Rits weer dicht</button></div>
</div>
<div class="rto__helft rto__helft--l" aria-hidden="true"></div>
<div class="rto__helft rto__helft--r" aria-hidden="true"></div>
<svg class="rto__tanden" aria-hidden="true"><path class="rto__open" d=""/><path class="rto__vast" d=""/></svg>
<p class="rto__hint" aria-hidden="true">Trek de rits omlaag</p>
<button type="button" class="rto__trek" role="slider" aria-label="Rits" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0" aria-valuetext="dicht"><span class="rto__schuif" aria-hidden="true"></span><span class="rto__lip" aria-hidden="true"></span></button>
</div>
</div>/* Rits die een actie onthult · © 2026 NedDev BV · NedDev Lab-licentie (gratis gebruik, niet doorverkopen) · neddev.com/nl/lab/gebruik/#licentie */
.rto{position:relative;width:100%;height:100%;overflow:hidden;background:#F5480F;font-family:Switzer,sans-serif;touch-action:pan-y}
.rto__binnen{position:absolute;inset:0;display:flex;flex-direction:column;justify-content:center;align-items:center;text-align:center;padding:18px 22px;box-sizing:border-box;color:#fff}
.rto__kop{margin:0;font-size:24px;font-weight:700;line-height:1.1;max-width:12em}
.rto__sub{margin:6px 0 12px;font-size:13px;line-height:1.35;max-width:26em}
.rto__rij{display:flex;gap:14px;align-items:center;justify-content:center;flex-wrap:wrap}
.rto__knop{font-size:13px;font-weight:600;min-height:30px;padding:0 14px;display:inline-flex;align-items:center;border-radius:4px;background:#fff;color:#0a0a0a;text-decoration:none;transition:background-color .15s,color .15s}
.rto__knop:hover{background:#0a0a0a;color:#fff}
.rto__dicht{font:inherit;font-size:13px;min-height:28px;padding:0;border:0;background:none;color:#fff;text-decoration:underline;text-underline-offset:3px;cursor:pointer}
.rto__knop:focus-visible,.rto__dicht:focus-visible{outline:2px solid #0a0a0a;outline-offset:2px}
.rto__helft{position:absolute;inset:0;background-color:#1b1a19;background-image:repeating-linear-gradient(45deg,rgba(255,255,255,.035) 0 2px,transparent 2px 6px);transition:transform .5s cubic-bezier(.5,0,.2,1),opacity .5s}
.rto__helft--l{box-shadow:inset -10px 0 14px -10px rgba(0,0,0,.6)}
.rto.is-open .rto__helft--l{transform:translateX(-62%);opacity:0}
.rto.is-open .rto__helft--r{transform:translateX(62%);opacity:0}
.rto__tanden{position:absolute;inset:0;width:100%;height:100%;pointer-events:none;transition:opacity .3s}
.rto__open,.rto__vast{fill:none;stroke:#c9c6c0;stroke-dasharray:3 3}
.rto__open{stroke-width:6}
.rto__vast{stroke-width:9}
.rto__hint{position:absolute;top:22px;margin:0;font-size:13px;color:#fff;pointer-events:none;transition:opacity .2s}
.rto__trek{position:absolute;left:0;top:0;width:32px;height:52px;margin-left:-16px;padding:0;border:0;background:none;cursor:grab;touch-action:none}
.rto__trek:active{cursor:grabbing}
.rto__schuif{position:absolute;left:8px;top:0;width:16px;height:14px;border-radius:4px;background:#d6d3ce;box-shadow:inset 0 -3px 0 rgba(0,0,0,.18)}
.rto__lip{position:absolute;left:10px;top:11px;width:12px;height:34px;border-radius:4px;background:#e9e6e1;box-shadow:inset 0 0 0 3px #d6d3ce,0 3px 6px rgba(0,0,0,.35)}
.rto__lip::after{content:"";position:absolute;left:4px;top:20px;width:4px;height:8px;border-radius:2px;background:#1b1a19}
.rto__trek:focus-visible{outline:2px solid #F5480F;outline-offset:2px;border-radius:4px}
.rto.is-open .rto__tanden,.rto.is-open .rto__trek,.rto.is-open .rto__hint{opacity:0;pointer-events:none}
.rto.is-bezig .rto__hint{opacity:0}
@media (max-width:420px){.rto__kop{font-size:20px}.rto__binnen{padding:14px 16px}}
@media (prefers-reduced-motion:reduce){.rto__helft,.rto__tanden,.rto__hint{transition:none}}// Rits die een actie onthult · © 2026 NedDev BV · NedDev Lab-licentie (gratis gebruik, niet doorverkopen) · neddev.com/nl/lab/gebruik/#licentie
// root = het element waarin de HTML staat
function init(root){
const w = root.querySelector('.rto'); if (!w) return;
const L = w.querySelector('.rto__helft--l'), R = w.querySelector('.rto__helft--r'), svg = w.querySelector('.rto__tanden');
const open = w.querySelector('.rto__open'), vast = w.querySelector('.rto__vast'), trek = w.querySelector('.rto__trek');
const binnen = w.querySelector('.rto__binnen'), hint = w.querySelector('.rto__hint'), dicht = w.querySelector('.rto__dicht');
const rm = matchMedia('(prefers-reduced-motion: reduce)').matches;
let p = 0, W = 0, H = 0, anim = 0, sleep = null, getoond = false;
function teken(){
const r = w.getBoundingClientRect(); W = r.width; H = r.height; if (!W) return;
const cx = W / 2, y0 = 16, y = y0 + p * (H - y0 + 30), b = p * cx * 0.95;
svg.setAttribute('viewBox', '0 0 ' + W + ' ' + H);
L.style.clipPath = 'polygon(0 0,' + (cx - b).toFixed(1) + 'px 0,' + cx + 'px ' + y.toFixed(1) + 'px,' + cx + 'px 100%,0 100%)';
R.style.clipPath = 'polygon(' + (cx + b).toFixed(1) + 'px 0,100% 0,100% 100%,' + cx + 'px 100%,' + cx + 'px ' + y.toFixed(1) + 'px)';
open.setAttribute('d', 'M' + (cx - b - 3) + ' -4 L' + (cx - 3) + ' ' + y + ' M' + (cx + b + 3) + ' -4 L' + (cx + 3) + ' ' + y);
vast.setAttribute('d', 'M' + cx + ' ' + y + ' V' + (H + 4));
trek.style.transform = 'translate(' + cx + 'px,' + (Math.min(y, H - 20) - 7).toFixed(1) + 'px)';
hint.style.left = (cx + 24) + 'px';
const pr = Math.round(p * 100);
trek.setAttribute('aria-valuenow', String(Math.min(100, pr)));
trek.setAttribute('aria-valuetext', pr >= 100 ? 'helemaal open' : pr === 0 ? 'dicht' : pr + ' procent open');
}
function naar(doel, ms, klaar){
cancelAnimationFrame(anim);
if (rm) { p = doel; teken(); if (klaar) klaar(); return; }
const van = p, t0 = performance.now();
(function f(t){
const k = Math.min(1, (t - t0) / ms);
p = van + (doel - van) * (1 - Math.pow(1 - k, 3)); teken();
if (k < 1) anim = requestAnimationFrame(f); else if (klaar) klaar();
})(t0);
}
function helemaal(){
w.classList.add('is-bezig');
naar(1, 320, () => { w.classList.add('is-open'); binnen.inert = false; const k = w.querySelector('.rto__knop'); if (document.activeElement === trek) k.focus(); });
}
function sluit(){
w.classList.remove('is-open', 'is-bezig'); binnen.inert = true; p = 1; teken();
setTimeout(() => naar(0, 450, () => trek.focus()), rm ? 0 : 380);
}
function loslaten(){ if (p > 0.5) helemaal(); else naar(0, 380, () => w.classList.remove('is-bezig')); }
trek.addEventListener('pointerdown', e => {
if (e.button > 0) return; e.preventDefault(); cancelAnimationFrame(anim);
sleep = { id: e.pointerId, sy: e.clientY, p0: p, ver: 0 }; trek.setPointerCapture(e.pointerId); w.classList.add('is-bezig');
});
trek.addEventListener('pointermove', e => {
if (!sleep || e.pointerId !== sleep.id) return;
const dy = e.clientY - sleep.sy; sleep.ver = Math.max(sleep.ver, Math.abs(dy));
p = Math.max(0, Math.min(1, sleep.p0 + dy / (H - 16))); teken();
});
const eind = () => { if (!sleep) return; const kort = sleep.ver < 5; sleep = null; if (kort) helemaal(); else loslaten(); };
trek.addEventListener('pointerup', eind); trek.addEventListener('pointercancel', eind);
trek.addEventListener('keydown', e => {
const k = e.key;
if (k === 'ArrowDown' || k === 'ArrowRight') { e.preventDefault(); w.classList.add('is-bezig'); p = Math.min(1, p + 0.1); teken(); if (p >= 1) helemaal(); }
else if (k === 'ArrowUp' || k === 'ArrowLeft') { e.preventDefault(); p = Math.max(0, p - 0.1); teken(); if (p === 0) w.classList.remove('is-bezig'); }
else if (k === 'End' || k === 'Enter' || k === ' ') { e.preventDefault(); helemaal(); }
else if (k === 'Home') { e.preventDefault(); naar(0, 300, () => w.classList.remove('is-bezig')); }
});
dicht.addEventListener('click', sluit);
w.querySelector('.rto__knop').addEventListener('click', e => e.preventDefault());
teken();
if ('ResizeObserver' in window) new ResizeObserver(teken).observe(w);
if (!rm && 'IntersectionObserver' in window) new IntersectionObserver(es => {
if (es[0].isIntersecting && !getoond && p === 0) { getoond = true; setTimeout(() => { if (p === 0 && !sleep) naar(0.14, 380, () => setTimeout(() => { if (!sleep && p < 0.2) naar(0, 420); }, 250)); }, 600); }
}, { threshold: 0.6 }).observe(w);
}
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 →