Actiemenu met popover Nieuw
Actiemenu achter drie puntjes op de Popover-API (light dismiss, Esc, top layer) met CSS anchor positioning en een zachte entree via @starting-style. Pijltjes, Home, End en Tab werken, de gevaarlijke actie staat los onderaan in rood en een actie die nog niet mag staat uitgeschakeld. Fallback: zonder anchor positioning rekent een klein script de plek uit, zonder Popover-API opent het menu als gewone laag in de rij.
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.
<!-- Actiemenu met popover · NedDev · eigen werk. -->
<div class="jouw-container">
<div class="pmn">
<p class="pmn__cap">Recente documenten</p>
<ul class="pmn__rows">
<li class="pmn__row"><span class="pmn__doc"><b>Offerte badkamer fam. Jansen</b><span>€ 8.420,00 · ma 13 okt</span></span><button type="button" class="pmn__trig" aria-haspopup="menu" aria-expanded="false" aria-label="Acties voor offerte badkamer fam. Jansen"><svg viewBox="0 0 20 20" aria-hidden="true"><circle cx="4.5" cy="10" r="1.6"/><circle cx="10" cy="10" r="1.6"/><circle cx="15.5" cy="10" r="1.6"/></svg></button>
<div class="pmn__menu" popover role="menu"><button type="button" role="menuitem" class="pmn__it" data-act="Bewerken">Bewerken<kbd>E</kbd></button><button type="button" role="menuitem" class="pmn__it" data-act="Gedupliceerd">Dupliceren<kbd>D</kbd></button><button type="button" role="menuitem" class="pmn__it" data-act="Pdf gedownload">Downloaden als pdf</button><button type="button" role="menuitem" class="pmn__it" aria-disabled="true" title="Kan pas na akkoord van de klant">Omzetten naar factuur</button><span class="pmn__sep" role="separator"></span><button type="button" role="menuitem" class="pmn__it pmn__it--danger" data-act="Verwijderd">Verwijderen</button></div></li>
<li class="pmn__row"><span class="pmn__doc"><b>Offerte dakkapel Prinsenstraat</b><span>€ 11.950,00 · vr 10 okt</span></span><button type="button" class="pmn__trig" aria-haspopup="menu" aria-expanded="false" aria-label="Acties voor offerte dakkapel Prinsenstraat"><svg viewBox="0 0 20 20" aria-hidden="true"><circle cx="4.5" cy="10" r="1.6"/><circle cx="10" cy="10" r="1.6"/><circle cx="15.5" cy="10" r="1.6"/></svg></button>
<div class="pmn__menu" popover role="menu"><button type="button" role="menuitem" class="pmn__it" data-act="Bewerken">Bewerken<kbd>E</kbd></button><button type="button" role="menuitem" class="pmn__it" data-act="Gedupliceerd">Dupliceren<kbd>D</kbd></button><button type="button" role="menuitem" class="pmn__it" data-act="Pdf gedownload">Downloaden als pdf</button><button type="button" role="menuitem" class="pmn__it" data-act="Omgezet naar factuur">Omzetten naar factuur</button><span class="pmn__sep" role="separator"></span><button type="button" role="menuitem" class="pmn__it pmn__it--danger" data-act="Verwijderd">Verwijderen</button></div></li>
<li class="pmn__row"><span class="pmn__doc"><b>Factuur onderhoud cv-ketel</b><span>€ 189,00 · wo 8 okt</span></span><button type="button" class="pmn__trig" aria-haspopup="menu" aria-expanded="false" aria-label="Acties voor factuur onderhoud cv-ketel"><svg viewBox="0 0 20 20" aria-hidden="true"><circle cx="4.5" cy="10" r="1.6"/><circle cx="10" cy="10" r="1.6"/><circle cx="15.5" cy="10" r="1.6"/></svg></button>
<div class="pmn__menu" popover role="menu"><button type="button" role="menuitem" class="pmn__it" data-act="Herinnering verstuurd">Herinnering sturen</button><button type="button" role="menuitem" class="pmn__it" data-act="Pdf gedownload">Downloaden als pdf</button><span class="pmn__sep" role="separator"></span><button type="button" role="menuitem" class="pmn__it pmn__it--danger" data-act="Gecrediteerd">Crediteren</button></div></li>
</ul>
<p class="pmn__status" role="status"></p>
</div>
</div>/* Actiemenu met popover · NedDev · eigen werk. */
.pmn{position:absolute;inset:0;overflow:auto;padding:12px 14px;box-sizing:border-box;background:#f4f3f1;color:#0a0a0a;font-family:'Switzer',system-ui,sans-serif;text-align:left}
.pmn *,.pmn *::before,.pmn *::after{box-sizing:border-box}
.pmn__cap{margin:0 0 6px;font:500 10px/1.2 'JetBrains Mono',ui-monospace,monospace;letter-spacing:.08em;text-transform:uppercase;color:#5c5a57}
.pmn__rows{list-style:none;margin:0;padding:0;background:#fff;border:1px solid #e0ddd8;border-radius:6px}
.pmn__row{position:relative;display:flex;align-items:center;gap:10px;padding:9px 8px 9px 12px;border-bottom:1px solid #eeebe7;transition:opacity .2s}
.pmn__row:last-child{border-bottom:0}
.pmn__row.is-gone{opacity:.4}
.pmn__row.is-gone b{text-decoration:line-through}
.pmn__doc{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}
.pmn__doc b{font-size:13.5px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.pmn__doc span{font:500 11px/1.2 'JetBrains Mono',ui-monospace,monospace;color:#5c5a57;font-variant-numeric:tabular-nums}
.pmn__trig{flex:none;width:32px;height:32px;display:grid;place-items:center;border:1px solid transparent;border-radius:4px;background:none;color:#3d3b38;cursor:pointer;transition:background .15s,border-color .15s,color .15s}
.pmn__trig svg{width:18px;height:18px;fill:currentColor}
.pmn__trig:hover{background:#f4f3f1;color:#0a0a0a}
.pmn__trig[aria-expanded="true"]{background:#0a0a0a;color:#fff}
.pmn__trig:focus-visible{outline:2px solid #F5480F;outline-offset:2px}
.pmn__menu{margin:0;inset:auto;width:212px;padding:5px;border:1px solid #d6d2cc;border-radius:6px;background:#fff;color:#0a0a0a;font-family:'Switzer',system-ui,sans-serif;box-shadow:0 14px 34px rgba(10,10,10,.16),0 2px 6px rgba(10,10,10,.06);opacity:1;transform:none;transform-origin:top right;transition:opacity .15s ease,transform .15s ease,overlay .15s allow-discrete,display .15s allow-discrete}
.pmn__menu:not(:popover-open){opacity:0;transform:translateY(-4px) scale(.98)}
@starting-style{.pmn__menu:popover-open{opacity:0;transform:translateY(-4px) scale(.98)}}
@supports (anchor-name:--a){.pmn__menu.pmn__menu--anc{position-area:bottom span-left;margin-top:4px;position-try-fallbacks:flip-block}}
.pmn__it{display:flex;align-items:center;justify-content:space-between;width:100%;font:500 13px/1.2 'Switzer',system-ui,sans-serif;color:#0a0a0a;text-align:left;background:none;border:0;border-radius:4px;padding:8px 9px;cursor:pointer}
.pmn__it kbd{font:500 10px/1 'JetBrains Mono',ui-monospace,monospace;color:#5c5a57;border:1px solid #e0ddd8;border-radius:3px;padding:2px 4px}
.pmn__it:hover,.pmn__it:focus-visible{background:#f4f3f1;outline:none}
.pmn__it:focus-visible{box-shadow:inset 2px 0 0 #F5480F}
.pmn__it[aria-disabled="true"]{color:#8a8781;cursor:not-allowed}
.pmn__it[aria-disabled="true"]:hover{background:none}
.pmn__it--danger{color:#B42318}
.pmn__it--danger:hover,.pmn__it--danger:focus-visible{background:#fdf0ee}
.pmn__sep{display:block;height:1px;margin:4px 2px;background:#eeebe7}
.pmn__status{margin:8px 0 0;min-height:16px;font-size:12px;color:#3d3b38}
.pmn--nopop .pmn__menu{position:absolute;right:8px;top:calc(100% - 4px);z-index:5;display:none}
.pmn--nopop .pmn__menu.is-open{display:block;opacity:1;transform:none}
@media (prefers-reduced-motion:reduce){.pmn__menu{transition:none}}// Actiemenu met popover · NedDev · eigen werk.
// root = het element waarin de HTML staat
function init(root){
var w=root.querySelector('.pmn');if(!w)return;
var status=w.querySelector('.pmn__status'),uid='pmn'+Math.random().toString(36).slice(2,8);
var hasPop=typeof HTMLElement!=='undefined'&&HTMLElement.prototype.hasOwnProperty('popover');
var hasAnc=window.CSS&&CSS.supports&&CSS.supports('anchor-name:--a')&&CSS.supports('position-area:bottom');
if(!hasPop)w.classList.add('pmn--nopop');
w.querySelectorAll('.pmn__row').forEach(function(row,i){
var btn=row.querySelector('.pmn__trig'),menu=row.querySelector('.pmn__menu'),its=[].slice.call(menu.querySelectorAll('.pmn__it'));
menu.id=uid+'m'+i;btn.setAttribute('aria-controls',menu.id);menu.setAttribute('aria-label',btn.getAttribute('aria-label'));
var enabled=function(){return its.filter(function(x){return x.getAttribute('aria-disabled')!=='true';});};
its.forEach(function(x){x.tabIndex=-1;});
function isOpen(){return hasPop?menu.matches(':popover-open'):menu.classList.contains('is-open');}
function place(){if(hasAnc)return;var r=btn.getBoundingClientRect(),mh=menu.offsetHeight,mw=menu.offsetWidth;var top=r.bottom+4;if(top+mh>innerHeight-8&&r.top-mh-4>8)top=r.top-mh-4;menu.style.top=top+'px';menu.style.left=Math.max(8,r.right-mw)+'px';}
function first(){var e=enabled();if(e[0])e[0].focus();}
function opened(){btn.setAttribute('aria-expanded','true');place();first();}
function closed(refocus){btn.setAttribute('aria-expanded','false');if(refocus)btn.focus();}
function show(){if(hasPop){if(!isOpen())menu.showPopover();}else{menu.classList.add('is-open');opened();}}
function hide(refocus){if(hasPop){if(isOpen())menu.hidePopover();if(refocus)btn.focus();}else{menu.classList.remove('is-open');closed(refocus);}}
if(hasPop){
if(hasAnc){btn.style.setProperty('anchor-name','--'+uid+i);menu.style.setProperty('position-anchor','--'+uid+i);menu.classList.add('pmn__menu--anc');}
else{menu.style.position='fixed';}
btn.popoverTargetElement=menu;
menu.addEventListener('toggle',function(e){if(e.newState==='open')opened();else{closed(menu.contains(document.activeElement));}});
if(!hasAnc)window.addEventListener('scroll',function(){if(isOpen())hide(false);},true);
}else{
btn.addEventListener('click',function(){isOpen()?hide(false):show();});
document.addEventListener('pointerdown',function(e){if(isOpen()&&!row.contains(e.target))hide(false);});
}
btn.addEventListener('keydown',function(e){if(e.key==='ArrowDown'||e.key==='ArrowUp'){e.preventDefault();show();if(e.key==='ArrowUp'){var en=enabled();en[en.length-1].focus();}}});
menu.addEventListener('keydown',function(e){var en=enabled(),i=en.indexOf(document.activeElement);
if(e.key==='ArrowDown'){e.preventDefault();en[(i+1)%en.length].focus();}
else if(e.key==='ArrowUp'){e.preventDefault();en[(i-1+en.length)%en.length].focus();}
else if(e.key==='Home'){e.preventDefault();en[0].focus();}
else if(e.key==='End'){e.preventDefault();en[en.length-1].focus();}
else if(e.key==='Escape'){e.preventDefault();hide(true);}
else if(e.key==='Tab'){hide(false);btn.focus();}
else if(e.key.length===1){var k=e.key.toLowerCase();var m=en.filter(function(x){return x.textContent.trim().toLowerCase().indexOf(k)===0;})[0];if(m)m.focus();}});
its.forEach(function(x){x.addEventListener('click',function(){if(x.getAttribute('aria-disabled')==='true'){status.textContent='Omzetten kan pas na akkoord van de klant.';return;}
var name=row.querySelector('.pmn__doc b').textContent,act=x.getAttribute('data-act');hide(true);
if(x.classList.contains('pmn__it--danger'))row.classList.add('is-gone');
status.textContent=act+': '+name+(x.classList.contains('pmn__it--danger')?' (demo)':'')+'.';});});
});
}
init(document.querySelector('.jouw-container'));
Meer uit navigatie & menu's
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 →