Contextmenu bij rechtsklik New
Custom context menu for a list of quotes: right-click, the menu key, Shift+F10 or a long press on a phone opens the menu at the cursor or below the row, always inside the area. Arrows, Home, End and Esc work and focus returns to the row. The actions really work: mark as sent, duplicate and delete. Use it in a web app or portal where people work with the same list a lot; also show the actions somewhere visible, since not everyone knows right-click.
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.
<!-- Contextmenu bij rechtsklik · NedDev · eigen werk. -->
<div class="jouw-container">
<div class="cmr">
<p class="cmr__hint">Rechtsklik op een offerte, of houd hem op je telefoon even vast.</p>
<ul class="cmr__list" aria-label="Offertes">
<li><button class="cmr__row" type="button" aria-haspopup="menu"><span class="cmr__nm">Dakkapel Van Leeuwen, Gouda</span><span class="cmr__amt">€ 8.450</span><span class="cmr__tag">concept</span></button></li>
<li><button class="cmr__row" type="button" aria-haspopup="menu"><span class="cmr__nm">Badkamer Smits, Woerden</span><span class="cmr__amt">€ 14.900</span><span class="cmr__tag is-sent">verstuurd</span></button></li>
<li><button class="cmr__row" type="button" aria-haspopup="menu"><span class="cmr__nm">Kozijnen De Graaf, Bodegraven</span><span class="cmr__amt">€ 6.275</span><span class="cmr__tag">concept</span></button></li>
<li><button class="cmr__row" type="button" aria-haspopup="menu"><span class="cmr__nm">Schilderwerk Yilmaz, Alphen</span><span class="cmr__amt">€ 3.180</span><span class="cmr__tag">concept</span></button></li>
</ul>
<div class="cmr__menu" role="menu" aria-label="Acties voor offerte" hidden>
<button class="cmr__mi" type="button" role="menuitem" tabindex="-1" data-a="open">Openen</button>
<button class="cmr__mi" type="button" role="menuitem" tabindex="-1" data-a="sent">Markeer als verstuurd</button>
<button class="cmr__mi" type="button" role="menuitem" tabindex="-1" data-a="dup">Dupliceren</button>
<span class="cmr__sep" role="separator"></span>
<button class="cmr__mi cmr__mi--del" type="button" role="menuitem" tabindex="-1" data-a="del">Verwijderen</button>
</div>
<p class="cmr__st" aria-live="polite"></p>
</div>
</div>/* Contextmenu bij rechtsklik · NedDev · eigen werk. */
.cmr{position:absolute;inset:0;display:flex;flex-direction:column;padding:12px 14px 8px;background:#f4f3f1;color:#0a0a0a;font-family:'Switzer',sans-serif;text-align:left;overflow:hidden}
.cmr *,.cmr *::before,.cmr *::after{box-sizing:border-box}
.cmr__hint{margin:0 0 8px;font:400 13px/1.35 'Switzer',sans-serif;color:#57534e}
.cmr__list{list-style:none;margin:0;padding:0;background:#fff;border:1px solid rgba(10,10,10,.12);border-radius:8px;overflow:auto;min-height:0}
.cmr__list li+li{border-top:1px solid rgba(10,10,10,.08)}
.cmr__row{display:flex;align-items:center;gap:10px;width:100%;min-height:36px;margin:0;padding:6px 10px;border:0;background:none;color:inherit;font:400 14px/1.2 'Switzer',sans-serif;text-align:left;cursor:default;-webkit-touch-callout:none;user-select:none}
.cmr__row:hover{background:#f4f3f1}
.cmr__row.is-ctx{background:#0a0a0a;color:#fff}
.cmr__row:focus-visible{outline:2px solid #F5480F;outline-offset:-2px}
.cmr__nm{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.cmr__amt{font-variant-numeric:tabular-nums;white-space:nowrap}
.cmr__tag{flex:none;padding:3px 6px;border-radius:4px;background:#f4f3f1;color:#57534e;font:500 12px/1 'Switzer',sans-serif}
.cmr__tag.is-sent{background:#0a0a0a;color:#fff}
.cmr__row.is-ctx .cmr__tag{background:#F5480F;color:#0a0a0a}
.cmr__row.is-new{animation:cmrIn .3s ease}
@keyframes cmrIn{from{background:#fde3d9}to{background:transparent}}
.cmr__menu{position:absolute;z-index:3;min-width:190px;padding:4px;background:#fff;border:1px solid rgba(10,10,10,.14);border-radius:8px;box-shadow:0 14px 30px -12px rgba(10,10,10,.4);transform-origin:var(--ox,0) var(--oy,0);animation:cmrMenu .14s ease-out}
.cmr__menu[hidden]{display:none}
@keyframes cmrMenu{from{opacity:0;transform:scale(.96)}to{opacity:1;transform:none}}
.cmr__mi{display:block;width:100%;min-height:30px;margin:0;padding:6px 10px;border:0;border-radius:4px;background:none;color:#0a0a0a;font:400 14px/1.2 'Switzer',sans-serif;text-align:left;cursor:pointer}
.cmr__mi:hover,.cmr__mi:focus{background:#f4f3f1;outline:none}
.cmr__mi:focus-visible{box-shadow:inset 0 0 0 2px #F5480F}
.cmr__mi--del{color:#b42318}
.cmr__sep{display:block;height:1px;margin:4px 6px;background:rgba(10,10,10,.1)}
.cmr__st{margin:6px 0 0;min-height:17px;font:400 13px/1.3 'Switzer',sans-serif;color:#0a0a0a}
@media (max-width:420px){.cmr__amt{display:none}}
@media (prefers-reduced-motion:reduce){.cmr__menu,.cmr__row.is-new{animation:none}}// Contextmenu bij rechtsklik · NedDev · eigen werk.
// root = het element waarin de HTML staat
function init(root){
var w=root.querySelector('.cmr');if(!w)return;
var list=w.querySelector('.cmr__list'),menu=w.querySelector('.cmr__menu'),st=w.querySelector('.cmr__st');
var mis=[].slice.call(menu.querySelectorAll('.cmr__mi')),row=null,lp=null,lpFired=false,openedAt=0;
function nm(r){return r.querySelector('.cmr__nm').textContent;}
function close(back){if(menu.hidden)return;menu.hidden=true;if(row){row.classList.remove('is-ctx');row.removeAttribute('aria-expanded');if(back)row.focus();}}
function open(r,x,y){close(false);row=r;openedAt=Date.now();r.classList.add('is-ctx');r.setAttribute('aria-expanded','true');
var tag=r.querySelector('.cmr__tag');mis[1].textContent=tag.classList.contains('is-sent')?'Terugzetten naar concept':'Markeer als verstuurd';
menu.hidden=false;var wr=w.getBoundingClientRect(),rr=r.getBoundingClientRect(),mw=menu.offsetWidth,mh=menu.offsetHeight;
if(x==null){x=rr.left-wr.left+16;y=rr.bottom-wr.top-4;}
var nx=Math.max(4,Math.min(x,wr.width-mw-4)),ny=Math.max(4,Math.min(y,wr.height-mh-4));
menu.style.left=nx+'px';menu.style.top=ny+'px';menu.style.setProperty('--ox',(x-nx)+'px');menu.style.setProperty('--oy',(y-ny)+'px');mis[0].focus();}
list.addEventListener('contextmenu',function(e){var r=e.target.closest('.cmr__row');if(!r)return;e.preventDefault();clearTimeout(lp);if(lpFired){lpFired=false;return;}if(!menu.hidden&&row===r&&Date.now()-openedAt<400)return;
var wr=w.getBoundingClientRect(),kb=e.button!==2&&e.pointerType!=='touch'&&e.pointerType!=='pen';
if(kb)open(r);else open(r,e.clientX-wr.left,e.clientY-wr.top);});
list.addEventListener('keydown',function(e){var r=e.target.closest('.cmr__row');if(!r)return;if(e.key==='ContextMenu'||(e.shiftKey&&e.key==='F10')){e.preventDefault();open(r);}
else if(e.key==='ArrowDown'||e.key==='ArrowUp'){var rs=[].slice.call(list.querySelectorAll('.cmr__row')),i=rs.indexOf(r),n=rs[i+(e.key==='ArrowDown'?1:-1)];if(n){e.preventDefault();n.focus();}}});
list.addEventListener('click',function(e){var r=e.target.closest('.cmr__row');if(!r||lpFired){lpFired=false;return;}st.textContent=nm(r)+': rechtsklik of Shift+F10 voor acties.';});
list.addEventListener('pointerdown',function(e){if(e.pointerType==='mouse')return;var r=e.target.closest('.cmr__row');if(!r)return;var wr=w.getBoundingClientRect(),x=e.clientX-wr.left,y=e.clientY-wr.top;clearTimeout(lp);lp=setTimeout(function(){lpFired=true;open(r,x,y);},520);});
['pointerup','pointercancel','pointermove'].forEach(function(t){list.addEventListener(t,function(e){if(t==='pointermove'&&e.pointerType==='mouse')return;clearTimeout(lp);});});
menu.addEventListener('keydown',function(e){var i=mis.indexOf(document.activeElement),k=e.key;
if(k==='ArrowDown'){e.preventDefault();mis[(i+1)%mis.length].focus();}else if(k==='ArrowUp'){e.preventDefault();mis[(i-1+mis.length)%mis.length].focus();}
else if(k==='Home'){e.preventDefault();mis[0].focus();}else if(k==='End'){e.preventDefault();mis[mis.length-1].focus();}
else if(k==='Escape'){e.preventDefault();close(true);}else if(k==='Tab'){e.preventDefault();close(true);}});
menu.addEventListener('click',function(e){var b=e.target.closest('.cmr__mi');if(!b||!row)return;var a=b.dataset.a,r=row,n=nm(r);
if(a==='open'){close(true);st.textContent='Offerte '+n+' geopend.';}
else if(a==='sent'){var t=r.querySelector('.cmr__tag'),on=!t.classList.contains('is-sent');t.classList.toggle('is-sent',on);t.textContent=on?'verstuurd':'concept';close(true);st.textContent=n+(on?' staat nu op verstuurd.':' staat weer op concept.');}
else if(a==='dup'){var li=r.parentElement,c=li.cloneNode(true),cr=c.querySelector('.cmr__row');cr.classList.remove('is-ctx');cr.removeAttribute('aria-expanded');cr.querySelector('.cmr__nm').textContent=n+' (kopie)';var ct=cr.querySelector('.cmr__tag');ct.classList.remove('is-sent');ct.textContent='concept';cr.classList.add('is-new');li.after(c);close(false);cr.focus();st.textContent='Kopie gemaakt van '+n+'.';}
else if(a==='del'){var li2=r.parentElement,nx=li2.nextElementSibling||li2.previousElementSibling;close(false);li2.remove();st.textContent='Offerte '+n+' verwijderd.';if(nx)nx.querySelector('.cmr__row').focus();}});
document.addEventListener('pointerdown',function(e){if(!menu.hidden&&!menu.contains(e.target))close(false);});
w.addEventListener('scroll',function(){if(Date.now()-openedAt>400)close(false);},true);
}
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 →