Datumbereik met twee maanden Nieuw
Aankomst en vertrek kiezen over twee maanden naast elkaar, voor een B&B, vakantiehuis of verhuur. Na de eerste klik toont hover (of het toetsenbord) het bereik vooraf met aantal nachten en prijs; korter dan 2 nachten of over een volgeboekte nacht heen wordt rood met uitleg. ARIA-grid met pijltjes, Home/End en PageUp/PageDown. Smal scherm: één maand via een container query (fallback: twee maanden onder elkaar met scroll).
AankomstKies datum
VertrekKies datum
Verblijfmin. 2 nachten
Klik je aankomstdag. Doorgestreept is volgeboekt.
Zo gebruik je het
- Plak de HTML op de plek waar het onderdeel moet komen.
- Zet de CSS in je stylesheet. Alle klassen hebben een eigen voorvoegsel, dus niets botst met je eigen opmaak.
- Voeg de JavaScript toe en roep init() aan met het element waar de HTML in staat.
<!-- Datumbereik met twee maanden · NedDev · eigen werk. -->
<div class="jouw-container">
<div class="bdb"><div class="bdb__top"><div class="bdb__f"><span class="bdb__k">Aankomst</span><b class="bdb__a">Kies datum</b></div><div class="bdb__f"><span class="bdb__k">Vertrek</span><b class="bdb__v">Kies datum</b></div><div class="bdb__f bdb__f--n"><span class="bdb__k">Verblijf</span><b class="bdb__n">min. 2 nachten</b></div></div><div class="bdb__cal"><button type="button" class="bdb__nav bdb__prev" aria-label="Vorige maand">‹</button><button type="button" class="bdb__nav bdb__next" aria-label="Volgende maand">›</button><div class="bdb__ms"></div></div><div class="bdb__foot"><p class="bdb__msg" aria-live="polite">Klik je aankomstdag. Doorgestreept is volgeboekt.</p><button type="button" class="bdb__clr">Wissen</button><button type="button" class="bdb__ok" disabled>Toepassen</button></div></div>
</div>/* Datumbereik met twee maanden · NedDev · eigen werk. */
.bdb{position:absolute;inset:0;display:flex;flex-direction:column;gap:6px;padding:10px 12px;font-family:'Switzer',system-ui,sans-serif;color:#0a0a0a;background:#fff;overflow:auto;container-type:inline-size}
.bdb__top{display:grid;grid-template-columns:1fr 1fr 1.15fr;border:1px solid rgba(10,10,10,.16);border-radius:4px;flex:none}
.bdb__f{padding:4px 9px;min-width:0}
.bdb__f+.bdb__f{border-left:1px solid rgba(10,10,10,.12)}
.bdb__k{display:block;font:500 9px 'JetBrains Mono',monospace;text-transform:uppercase;letter-spacing:.07em;color:#5c5a56}
.bdb__f b{display:block;font-size:12.5px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-variant-numeric:tabular-nums}
.bdb__f b.is-empty{color:#76736d;font-weight:500}
.bdb__f--n b{color:#0a0a0a}
.bdb__cal{position:relative;flex:none}
.bdb__nav{position:absolute;top:-2px;width:24px;height:22px;border:0;border-radius:4px;background:none;font-size:17px;line-height:1;cursor:pointer;color:#0a0a0a;z-index:1;transition:background-color .15s}
.bdb__nav:hover{background:#f4f3f1}.bdb__nav:focus-visible{outline:2px solid #0a0a0a;outline-offset:0}.bdb__nav:disabled{color:#c0bdb7;cursor:default;background:none}
.bdb__prev{left:0}.bdb__next{right:0}
.bdb__ms{display:grid;grid-template-columns:1fr 1fr;gap:18px}
.bdb__mt{margin:0 0 2px;text-align:center;font-size:12px;font-weight:700;line-height:18px;text-transform:capitalize}
.bdb__g{width:100%;border-collapse:collapse;table-layout:fixed}
.bdb__g th{font:500 8.5px 'JetBrains Mono',monospace;color:#5c5a56;text-transform:uppercase;height:13px;padding:0}
.bdb__d{position:relative;height:19px;padding:0;text-align:center;font-size:11.5px;font-variant-numeric:tabular-nums;cursor:pointer;transition:background-color .1s}
.bdb__d:hover{background:#f4f3f1}
.bdb__d:focus-visible{outline:2px solid #0a0a0a;outline-offset:-2px;z-index:1}
.bdb__d.is-pad{visibility:hidden}
.bdb__d.is-past{color:#b5b2ac;cursor:not-allowed}
.bdb__d.is-full{color:#9a978f;text-decoration:line-through;text-decoration-color:#c4231b}
.bdb__d.is-in{background:#fde3d8}
.bdb__d.is-prev{background:#f4f3f1;box-shadow:inset 0 1px 0 rgba(10,10,10,.18),inset 0 -1px 0 rgba(10,10,10,.18)}
.bdb__d.is-bad{background:#fbe3e1;color:#c4231b;box-shadow:inset 0 0 0 1px #c4231b}
.bdb__d.is-s,.bdb__d.is-e{background:#0a0a0a;color:#fff;font-weight:600}
.bdb__d.is-s{border-radius:4px 0 0 4px}.bdb__d.is-e{border-radius:0 4px 4px 0}
.bdb__d.is-s.is-e{border-radius:4px}
.bdb__foot{display:flex;align-items:center;gap:6px;margin-top:auto;flex:none}
.bdb__msg{flex:1;margin:0;font-size:11px;line-height:1.3;color:#5c5a56;min-width:0}
.bdb.is-err .bdb__msg{color:#c4231b}.bdb.is-ok .bdb__msg{color:#1f7a3d}
.bdb__clr,.bdb__ok{flex:none;height:28px;padding:0 11px;border-radius:4px;font:600 12px 'Switzer',system-ui,sans-serif;cursor:pointer;transition:background-color .15s,color .15s,border-color .15s}
.bdb__clr{background:none;border:1px solid transparent;color:#0a0a0a;text-decoration:underline;text-underline-offset:2px}
.bdb__clr:hover{background:#f4f3f1}
.bdb__ok{background:#0a0a0a;color:#fff;border:1px solid #0a0a0a}
.bdb__ok:hover:not(:disabled){background:#F5480F;border-color:#F5480F;color:#0a0a0a}
.bdb__ok:active:not(:disabled){transform:translateY(1px)}
.bdb__ok:disabled{background:#e9e7e3;border-color:#e9e7e3;color:#76736d;cursor:not-allowed}
.bdb__clr:focus-visible,.bdb__ok:focus-visible{outline:2px solid #0a0a0a;outline-offset:2px}
@container (max-width:370px){.bdb__ms{grid-template-columns:1fr}.bdb__m+.bdb__m{display:none}.bdb__top{grid-template-columns:1fr 1fr}.bdb__f--n{display:none}}
@supports not (container-type:inline-size){.bdb__ms{grid-template-columns:repeat(auto-fit,minmax(160px,1fr))}}
@media (prefers-reduced-motion:reduce){.bdb *{transition:none!important}}// Datumbereik met twee maanden · NedDev · eigen werk.
// root = het element waarin de HTML staat
function init(root){
var w=root.querySelector('.bdb');if(!w)return;
var ms=w.querySelector('.bdb__ms'),eA=w.querySelector('.bdb__a'),eV=w.querySelector('.bdb__v'),eN=w.querySelector('.bdb__n'),msg=w.querySelector('.bdb__msg'),ok=w.querySelector('.bdb__ok'),prev=w.querySelector('.bdb__prev'),next=w.querySelector('.bdb__next');
var DAG=['zo','ma','di','wo','do','vr','za'],DAGL=['zondag','maandag','dinsdag','woensdag','donderdag','vrijdag','zaterdag'],MND=['januari','februari','maart','april','mei','juni','juli','augustus','september','oktober','november','december'],MK=['jan','feb','mrt','apr','mei','jun','jul','aug','sep','okt','nov','dec'];
var MIN=2,T=new Date();T.setHours(0,0,0,0);
function add(d,n){var x=new Date(d);x.setDate(x.getDate()+n);return x}
function key(d){return d.getFullYear()+'-'+d.getMonth()+'-'+d.getDate()}
function same(a,b){return !!a&&!!b&&a.getTime()===b.getTime()}
function nights(a,b){return Math.round((b-a)/864e5)}
/* volgeboekte nachten: in productie uit je reserveringssysteem */
var FULL={};[5,6,12,13,14,25,33,34].forEach(function(n){FULL[key(add(T,n))]=1});
function full(d){return !!FULL[key(d)]}
function rate(d){var g=d.getDay();return g===5||g===6?115:95}
function price(a,b){var s=0;for(var d=new Date(a);d<b;d=add(d,1))s+=rate(d);return s}
function eur(n){return '€ '+n.toLocaleString('nl-NL')}
function short(d){return DAG[d.getDay()]+' '+d.getDate()+' '+MK[d.getMonth()]}
function crosses(a,b){for(var d=new Date(a);d<b;d=add(d,1))if(full(d))return true;return false}
var start=null,end=null,hov=null,foc=add(T,1),base=new Date(T.getFullYear(),T.getMonth(),1);
function months(){return ms.querySelectorAll('.bdb__m').length&&getComputedStyle(ms.querySelectorAll('.bdb__m')[1]).display==='none'?1:2}
function check(a,b){var n=nights(a,b);if(n<MIN)return 'Minimaal '+MIN+' nachten. Kies vertrek vanaf '+short(add(a,MIN))+'.';if(crosses(a,b))return 'Er zit een volgeboekte nacht in deze periode.';return ''}
function state(s,t){w.classList.toggle('is-err',s==='err');w.classList.toggle('is-ok',s==='ok');msg.textContent=t}
function monthHtml(m){var s=add(m,-((m.getDay()+6)%7)),h='<div class="bdb__m"><h3 class="bdb__mt" id="bdb-t'+m.getMonth()+'">'+MND[m.getMonth()]+' '+m.getFullYear()+'</h3><table class="bdb__g" role="grid" aria-labelledby="bdb-t'+m.getMonth()+'"><thead><tr><th scope="col">ma</th><th scope="col">di</th><th scope="col">wo</th><th scope="col">do</th><th scope="col">vr</th><th scope="col">za</th><th scope="col">zo</th></tr></thead><tbody>';
for(var r=0;r<6;r++){h+='<tr>';for(var c=0;c<7;c++){var d=add(s,r*7+c);if(d.getMonth()!==m.getMonth()){h+='<td class="bdb__d is-pad" aria-hidden="true"></td>';continue}
var cl='bdb__d',past=d<T,lab=DAGL[d.getDay()]+' '+d.getDate()+' '+MND[d.getMonth()];
if(past)cl+=' is-past';if(full(d)){cl+=' is-full';lab+=', volgeboekt'}
if(same(d,start))cl+=' is-s';if(same(d,end))cl+=' is-e';
if(start&&end&&d>start&&d<end)cl+=' is-in';
if(start&&!end&&hov&&hov>start){if(d>start&&d<=hov){var bad=check(start,hov);cl+=bad?(same(d,hov)?' is-bad':' is-prev'):(same(d,hov)?' is-e':' is-prev')}}
h+='<td role="gridcell" class="'+cl+'" data-t="'+d.getTime()+'" tabindex="'+(same(d,foc)?0:-1)+'" aria-selected="'+((same(d,start)||same(d,end))?'true':'false')+'"'+(past?' aria-disabled="true"':'')+' aria-label="'+lab+'">'+d.getDate()+'</td>'}h+='</tr>'}
return h+'</tbody></table></div>'}
function render(){var m2=new Date(base.getFullYear(),base.getMonth()+1,1);ms.innerHTML=monthHtml(base)+monthHtml(m2);
prev.disabled=base.getFullYear()*12+base.getMonth()<=T.getFullYear()*12+T.getMonth();
var tgt=hov&&start&&!end?hov:end;
eA.textContent=start?short(start):'Kies datum';eA.classList.toggle('is-empty',!start);
eV.textContent=end?short(end):(start&&hov&&hov>start?short(hov):'Kies datum');eV.classList.toggle('is-empty',!end);
if(start&&tgt&&tgt>start){var n=nights(start,tgt),bad=check(start,tgt);eN.textContent=bad?n+(n===1?' nacht':' nachten'):n+' nachten · '+eur(price(start,tgt))}else eN.textContent='min. '+MIN+' nachten';
ok.disabled=!(start&&end)}
function focusCell(){var c=ms.querySelector('[tabindex="0"]');if(c&&c.offsetParent)c.focus()}
function preview(d){if(start&&!end){hov=d;if(d>start){var b=check(start,d);state(b?'err':'',b||(nights(start,d)+' nachten, '+eur(price(start,d))+'. Klik om te bevestigen.'))}render()}}
function pick(d){if(d<T){state('err','Deze datum ligt in het verleden.');return}
if(!start||end||d<=start){if(full(d)){state('err','Aankomst op '+short(d)+' kan niet: die nacht is volgeboekt.');return}start=d;end=null;hov=null;state('','Aankomst '+short(d)+'. Kies nu je vertrekdag (minimaal '+MIN+' nachten).');render();return}
var b=check(start,d);if(b){state('err',b);return}end=d;hov=null;state('ok',nights(start,end)+' nachten: '+short(start)+' t/m '+short(end)+', totaal '+eur(price(start,end))+'.');render()}
function goto(d){var vis=months(),last=new Date(base.getFullYear(),base.getMonth()+vis,0);
if(d<new Date(T.getFullYear(),T.getMonth(),1))return;foc=d;if(d<base)base=new Date(d.getFullYear(),d.getMonth(),1);else if(d>last)base=new Date(d.getFullYear(),d.getMonth()-vis+1,1);preview(d);render();focusCell()}
ms.addEventListener('click',function(e){var c=e.target.closest('td[data-t]');if(!c)return;var d=new Date(+c.dataset.t);foc=d;pick(d);render();focusCell()});
ms.addEventListener('pointerover',function(e){var c=e.target.closest('td[data-t]');if(!c||!start||end)return;var d=new Date(+c.dataset.t);if(!same(d,hov))preview(d)});
ms.addEventListener('keydown',function(e){var k=e.key,d=new Date(foc),dow=(d.getDay()+6)%7;
if(k==='ArrowLeft')d=add(d,-1);else if(k==='ArrowRight')d=add(d,1);else if(k==='ArrowUp')d=add(d,-7);else if(k==='ArrowDown')d=add(d,7);else if(k==='Home')d=add(d,-dow);else if(k==='End')d=add(d,6-dow);
else if(k==='PageUp'||k==='PageDown'){var x=new Date(d);x.setDate(1);x.setMonth(x.getMonth()+(k==='PageUp'?-1:1));x.setDate(Math.min(d.getDate(),new Date(x.getFullYear(),x.getMonth()+1,0).getDate()));d=x}
else if(k==='Enter'||k===' '){e.preventDefault();pick(foc);render();focusCell();return}else return;e.preventDefault();goto(d)});
prev.addEventListener('click',function(){base=new Date(base.getFullYear(),base.getMonth()-1,1);render()});
next.addEventListener('click',function(){base=new Date(base.getFullYear(),base.getMonth()+1,1);render()});
w.querySelector('.bdb__clr').addEventListener('click',function(){start=end=hov=null;state('','Klik je aankomstdag. Doorgestreept is volgeboekt.');render()});
ok.addEventListener('click',function(){if(start&&end)state('ok','Toegepast: '+short(start)+' t/m '+short(end)+'. We houden de kamer 15 minuten vast.')});
/* demo-startstand: een geldig bereik van 3 nachten */
var s0=add(T,8);while(full(s0)||crosses(s0,add(s0,3)))s0=add(s0,1);start=s0;end=add(s0,3);foc=new Date(s0);
state('ok','3 nachten: '+short(start)+' t/m '+short(end)+', totaal '+eur(price(start,end))+'. Klik een nieuwe aankomstdag om te wijzigen.');render();
}
init(document.querySelector('.jouw-container'));
Meer uit formulieren & invoer
Iets gezien dat je mooi vindt?
Zet het in je selectie en stuur hem op. Dan bouwen we je site eromheen, in jouw huisstijl.
Neem contact op →