Bottom sheet met filters New
Mobile bottom sheet for filters: drag the handle (pointer events with velocity) to half or full, or down to close. The handle is also a button (Enter, arrow up/down), Esc closes, focus stays inside and returns afterwards. The result count updates live and shows an empty state. Built on
Kappers in Rotterdam48 resultaten
- Salon NoordBergweg 112 · 1,2 km8,9
- Kapsalon KralingenOudedijk 40 · 2,0 km9,2
- Barbier aan de MaasMaasboulevard 7 · 2,6 km8,4
- Haarwerk DelfshavenSchiedamseweg 88 · 3,1 km8,7
How to use it
- Paste the HTML where the navigation & menus 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.
<!-- Bottom sheet met filters · NedDev · eigen werk. -->
<div class="jouw-container">
<div class="bsh">
<div class="bsh__app">
<div class="bsh__top"><div><span class="bsh__eyebrow">Kappers in Rotterdam</span><strong class="bsh__count"><span>48</span> resultaten</strong></div><button class="bsh__open" type="button" aria-haspopup="dialog">Filters <span class="bsh__fc">0</span></button></div>
<ul class="bsh__res">
<li><b>Salon Noord</b><span>Bergweg 112 · 1,2 km</span><em>8,9</em></li>
<li><b>Kapsalon Kralingen</b><span>Oudedijk 40 · 2,0 km</span><em>9,2</em></li>
<li><b>Barbier aan de Maas</b><span>Maasboulevard 7 · 2,6 km</span><em>8,4</em></li>
<li><b>Haarwerk Delfshaven</b><span>Schiedamseweg 88 · 3,1 km</span><em>8,7</em></li>
</ul>
</div>
<div class="bsh__scrim"></div>
<dialog class="bsh__sheet" aria-labelledby="bsh-h">
<div class="bsh__grip"><button class="bsh__handle" type="button" aria-label="Paneel vergroten"><span></span></button></div>
<div class="bsh__head"><h3 id="bsh-h" class="bsh__h">Filters</h3><button class="bsh__clear" type="button">Wis alles</button><button class="bsh__x" type="button" aria-label="Sluit filters"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M6 6l12 12M18 6 6 18"/></svg></button></div>
<div class="bsh__body">
<fieldset class="bsh__fs"><legend>Sorteer op</legend><div class="bsh__seg">
<label><input type="radio" name="bsh-s" value="a" checked><span>Afstand</span></label>
<label><input type="radio" name="bsh-s" value="b"><span>Cijfer</span></label>
<label><input type="radio" name="bsh-s" value="c"><span>Prijs</span></label></div></fieldset>
<fieldset class="bsh__fs"><legend>Open op</legend><div class="bsh__chips">
<label><input type="checkbox" data-f=".75"><span>Zaterdag</span></label>
<label><input type="checkbox" data-f=".5" data-k="avond"><span>Avond na 18:00</span></label>
<label><input type="checkbox" data-f=".25" data-k="zondag"><span>Zondag</span></label></div></fieldset>
<fieldset class="bsh__fs"><legend>Prijs knippen</legend><div class="bsh__chips" data-or="1">
<label><input type="checkbox" data-f=".45"><span>Tot € 25</span></label>
<label><input type="checkbox" data-f=".5"><span>€ 25 tot € 40</span></label>
<label><input type="checkbox" data-f=".3"><span>Vanaf € 40</span></label></div></fieldset>
<label class="bsh__sw"><span>Alleen cijfer 8 of hoger</span><input type="checkbox" role="switch" data-f=".6"></label>
</div>
<div class="bsh__foot"><button class="bsh__apply" type="button">Toon 48 resultaten</button></div>
</dialog>
</div>
</div>/* Bottom sheet met filters · NedDev · eigen werk. */
.bsh{--ink:#0a0a0a;--pop:#F5480F;--pd:#b83a0b;--line:rgba(10,10,10,.12);position:absolute;inset:0;overflow:hidden;font-family:'Switzer',system-ui,sans-serif;color:var(--ink);background:#f4f3f1}
.bsh__app{height:100%;display:flex;flex-direction:column}
.bsh__top{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:10px 14px;background:#fff;border-bottom:1px solid var(--line)}
.bsh__eyebrow{display:block;font:600 9.5px/1.2 'JetBrains Mono',monospace;letter-spacing:.12em;text-transform:uppercase;color:#5b5b5b}
.bsh__count{font-size:14px;font-variant-numeric:tabular-nums}
.bsh__open{display:inline-flex;align-items:center;gap:8px;padding:7px 7px 7px 12px;border-radius:100px;border:1px solid var(--ink);background:var(--ink);color:#fff;font:600 12px/1 'Switzer',system-ui,sans-serif;cursor:pointer;transition:background .15s}
.bsh__open:hover{background:#2a2a2a}
.bsh__fc{min-width:20px;height:20px;border-radius:10px;background:#fff;color:var(--ink);font:700 11px/20px 'JetBrains Mono',monospace;text-align:center}
.bsh__res{list-style:none;margin:0;padding:4px 14px;flex:1;overflow:auto}
.bsh__res li{display:grid;grid-template-columns:1fr auto;padding:7px 0;border-bottom:1px solid var(--line);font-size:13px}
.bsh__res span{grid-column:1;font-size:11.5px;color:#5b5b5b}
.bsh__res em{grid-row:1/3;grid-column:2;align-self:center;font:700 12px/1 'JetBrains Mono',monospace;font-style:normal;padding:5px 6px;border-radius:3px;background:var(--ink);color:#fff}
.bsh button:focus-visible,.bsh input:focus-visible+span{outline:2px solid var(--pd);outline-offset:2px}
.bsh__scrim{position:absolute;inset:0;background:rgba(10,10,10,.42);opacity:0;pointer-events:none;transition:opacity .25s}
.bsh.is-open .bsh__scrim{opacity:1;pointer-events:auto}
.bsh__sheet{position:absolute;left:0;right:0;top:auto;bottom:0;margin:0 auto;width:100%;max-width:480px;max-height:none;height:0;border:0;padding:0;border-radius:12px 12px 0 0;background:#fff;color:var(--ink);display:flex;flex-direction:column;overflow:hidden;box-shadow:0 -10px 30px rgba(0,0,0,.18);transition:height .32s cubic-bezier(.2,.8,.2,1)}
.bsh__sheet:not([open]){display:none}
.bsh.is-drag .bsh__sheet{transition:none}
.bsh__grip{flex:none;display:flex;justify-content:center;padding-top:4px;touch-action:none;cursor:grab}
.bsh__handle{width:56px;height:18px;border:0;background:none;padding:0;cursor:grab;display:grid;place-items:center;touch-action:none}
.bsh__handle span{width:36px;height:4px;border-radius:2px;background:#c9c5be;transition:background .15s}
.bsh__handle:hover span{background:#8a867f}
.bsh__head{flex:none;display:flex;align-items:center;gap:6px;padding:0 6px 6px 14px;border-bottom:1px solid var(--line);touch-action:none}
.bsh__h{margin:0;font-size:15px;font-weight:700;flex:1}
.bsh__clear{border:0;background:none;font:500 12px/1 'Switzer',system-ui,sans-serif;text-decoration:underline;text-underline-offset:3px;cursor:pointer;padding:6px;color:var(--ink)}
.bsh__clear:disabled{color:#8a8a8a;text-decoration:none;cursor:default}
.bsh__x{width:30px;height:30px;border:0;border-radius:4px;background:none;cursor:pointer;display:grid;place-items:center;color:var(--ink)}
.bsh__x:hover{background:#f0eeea}
.bsh__x svg{width:15px;height:15px;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round}
.bsh__body{flex:1;min-height:0;overflow:auto;overscroll-behavior:contain;padding:8px 14px}
.bsh__fs{border:0;margin:0 0 10px;padding:0;min-width:0}
.bsh__fs legend{padding:0;margin-bottom:6px;font:600 9.5px/1 'JetBrains Mono',monospace;letter-spacing:.12em;text-transform:uppercase;color:#5b5b5b}
.bsh__seg{display:grid;grid-template-columns:repeat(3,1fr);gap:6px}
.bsh__chips{display:flex;flex-wrap:wrap;gap:6px}
.bsh__seg label,.bsh__chips label{position:relative}
.bsh__seg input,.bsh__chips input{position:absolute;opacity:0;inset:0;margin:0;cursor:pointer}
.bsh__seg span,.bsh__chips span{display:block;padding:7px 10px;border:1px solid var(--line);border-radius:4px;font-size:12px;text-align:center;transition:background .15s,border-color .15s,color .15s}
.bsh__chips span{border-radius:100px}
.bsh__seg label:hover span,.bsh__chips label:hover span{border-color:var(--ink)}
.bsh__seg input:checked+span,.bsh__chips input:checked+span{background:var(--ink);border-color:var(--ink);color:#fff}
.bsh__sw{display:flex;align-items:center;justify-content:space-between;gap:10px;font-size:12.5px;padding:4px 0 6px;cursor:pointer}
.bsh__sw input{appearance:none;width:36px;height:20px;border-radius:10px;background:#d9d5ce;position:relative;cursor:pointer;margin:0;transition:background .15s;flex:none}
.bsh__sw input::after{content:'';position:absolute;top:2px;left:2px;width:16px;height:16px;border-radius:50%;background:#fff;transition:transform .15s}
.bsh__sw input:checked{background:var(--ink)}
.bsh__sw input:checked::after{transform:translateX(16px)}
.bsh__sw input:focus-visible{outline:2px solid var(--pd);outline-offset:2px}
.bsh__foot{flex:none;padding:8px 14px 10px;border-top:1px solid var(--line);background:#fff}
.bsh__apply{width:100%;padding:10px;border:0;border-radius:4px;background:var(--pop);color:var(--ink);font:700 13px/1 'Switzer',system-ui,sans-serif;cursor:pointer;font-variant-numeric:tabular-nums;transition:background .15s}
.bsh__apply:hover{background:#ff6a35}
.bsh__apply:disabled{background:#e3e1dd;color:#5b5b5b;cursor:not-allowed}
@media (prefers-reduced-motion:reduce){.bsh__sheet,.bsh__scrim{transition:none}}// Bottom sheet met filters · NedDev · eigen werk.
// root = het element waarin de HTML staat
function init(root){
var w=root.querySelector('.bsh');if(!w)return;
var U='-'+Math.random().toString(36).slice(2,7),hh=w.querySelector('.bsh__h');hh.id+=U;
var dlg=w.querySelector('.bsh__sheet');dlg.setAttribute('aria-labelledby',hh.id);w.querySelectorAll('input[type=radio]').forEach(function(r){r.name+=U;});
var app=w.querySelector('.bsh__app'),scrim=w.querySelector('.bsh__scrim'),handle=w.querySelector('.bsh__handle'),openBtn=w.querySelector('.bsh__open'),apply=w.querySelector('.bsh__apply'),clear=w.querySelector('.bsh__clear');
var reduce=matchMedia('(prefers-reduced-motion: reduce)').matches,snap='half',opener=null,BASE=48;
function H(){return w.clientHeight;}function px(s){return s==='full'?Math.round(H()*.94):s==='half'?Math.round(H()*.62):0;}
function setH(v){dlg.style.height=v+'px';}
function to(s){snap=s;setH(px(s));handle.setAttribute('aria-label',s==='full'?'Paneel verkleinen':'Paneel vergroten');}
function open(){opener=document.activeElement;if(dlg.open)return;dlg.show();w.classList.add('is-open');app.inert=true;setH(0);void dlg.offsetHeight;to('half');setTimeout(function(){handle.focus();},40);}
function close(){if(!dlg.open)return;w.classList.remove('is-open');setH(0);app.inert=false;setTimeout(function(){dlg.close();},reduce?0:300);(opener||openBtn).focus();}
function count(){var c=BASE,n=0,or=0,any=false,k={};w.querySelectorAll('.bsh__body input[type=checkbox]').forEach(function(i){if(!i.checked)return;n++;if(i.dataset.k)k[i.dataset.k]=1;if(i.closest('[data-or]')){or+=+i.dataset.f;any=true;}else c*=+i.dataset.f;});
if(any)c*=Math.min(1,or);c=(k.avond&&k.zondag)?0:Math.round(c);
apply.textContent=c?'Toon '+c+' resultaten':'Geen resultaten, wis een filter';apply.disabled=!c;clear.disabled=!n;w.querySelector('.bsh__fc').textContent=n;return c;}
w.querySelectorAll('.bsh__body input').forEach(function(i){i.addEventListener('change',count);});
clear.addEventListener('click',function(){w.querySelectorAll('.bsh__body input[type=checkbox]').forEach(function(i){i.checked=false;});count();});
apply.addEventListener('click',function(){var c=count();w.querySelector('.bsh__count span').textContent=c;close();});
openBtn.addEventListener('click',open);w.querySelector('.bsh__x').addEventListener('click',close);scrim.addEventListener('click',close);
// slepen: hoogte volgt de vinger, loslaten kiest het snappunt (met snelheid)
var drag=null;
function down(e){if(e.button)return;if(e.target.closest('.bsh__clear,.bsh__x'))return;drag={y:e.clientY,h:dlg.offsetHeight,t:performance.now(),ly:e.clientY,lt:performance.now(),v:0,moved:false,id:e.pointerId,el:e.currentTarget};e.currentTarget.setPointerCapture(e.pointerId);}
function move(e){if(!drag)return;var dy=e.clientY-drag.y;if(Math.abs(dy)>4){drag.moved=true;w.classList.add('is-drag');}
if(!drag.moved)return;var nh=drag.h-dy,mx=px('full');if(nh>mx)nh=mx+(nh-mx)*.25;setH(Math.max(0,nh));var now=performance.now();if(now-drag.lt>0){drag.v=(e.clientY-drag.ly)/(now-drag.lt);}drag.ly=e.clientY;drag.lt=now;}
function up(){if(!drag)return;var d=drag;drag=null;w.classList.remove('is-drag');if(!d.moved)return;d.el.dataset.dragged='1';setTimeout(function(){delete d.el.dataset.dragged;},50);
var h=dlg.offsetHeight,v=d.v;if(v>.6){if(snap==='full'&&h>px('half')*.6)to('half');else close();return;}if(v<-.6){to('full');return;}
var opts=[['closed',0],['half',px('half')],['full',px('full')]].sort(function(a,b){return Math.abs(a[1]-h)-Math.abs(b[1]-h);});if(opts[0][0]==='closed')close();else to(opts[0][0]);}
[w.querySelector('.bsh__grip'),w.querySelector('.bsh__head')].forEach(function(el){el.addEventListener('pointerdown',down);el.addEventListener('pointermove',move);el.addEventListener('pointerup',up);el.addEventListener('pointercancel',up);});
handle.addEventListener('click',function(){if(handle.dataset.dragged)return;to(snap==='full'?'half':'full');});
handle.addEventListener('keydown',function(e){if(e.key==='ArrowUp'){e.preventDefault();to('full');}else if(e.key==='ArrowDown'){e.preventDefault();if(snap==='full')to('half');else close();}});
dlg.addEventListener('keydown',function(e){if(e.key==='Escape'){e.preventDefault();close();return;}
if(e.key==='Tab'){var f=[].slice.call(dlg.querySelectorAll('button,input')).filter(function(x){return !x.disabled&&x.offsetParent!==null;});if(!f.length)return;var a=f[0],z=f[f.length-1];
if(e.shiftKey&&document.activeElement===a){e.preventDefault();z.focus();}else if(!e.shiftKey&&document.activeElement===z){e.preventDefault();a.focus();}}});
dlg.addEventListener('cancel',function(e){e.preventDefault();close();});
window.addEventListener('resize',function(){if(dlg.open)to(snap);});
count();
}
init(document.querySelector('.jouw-container'));
More navigation & menus
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 →