Menus Using Popover
An exploration of menus created with popover
A Basic Popover
Here is a basic popover. You can use these anytime you need something that overlays. Think a three dot menu, a login / user menu or settings of any kind.
<button popovertarget="menu-demo-popover">Menu</button>
<div popover id="menu-demo-popover" class="menu">
<ul class="menu-demo">
<li><a href="#">Settings</a></li>
<li><a href="#">My Profile</a></li>
<li><a href="#">Help</a></li>
<li><a href="#">Logout</a></li>
</ul>
</div>
So what’s the difference between popover and dialog? 1. Popover doesn’t inflict inert onto your page. 2. Dialog can be on toplayer or not, Popover is on toplayer.
Popover Actions
You can do a lot with just html here if all you need is basic show and hide. It’s only once we get into positioning & animations does this get a bit trickier.
<button popovertargetaction="show" popovertarget="ampd">Show</button>
<div popover id="ampd" class="menu">
<button popovertargetaction="hide" popovertarget="ampd">Hide</button>
</div>
Menu w/ Compatible Anchor
Let’s say I wanted everything above, but using only shippable features.
<script>
let button = document.getElementById('md');
let popover = document.getElementById('mdp');
button.addEventListener('click', toggle);
function update_position() {
const target_position = button.getBoundingClientRect();
popover.style.inset = 'unset';
popover.style.top = target_position.bottom + 'px';
popover.style.left = target_position.right - target_position.width + 'px';
}
const resizeObserver = new ResizeObserver(update_position);
resizeObserver.observe(popover);
window.addEventListener('resize', update_position);
window.addEventListener('scroll', update_position);
// Animation
function toggle() {
const is_opening = !popover.matches(':popover-open');
const translate = is_opening ? ['0 10px', '0 0'] : ['0 0', '0 10px'];
const opacity = is_opening ? [0, 1] : [1, 0];
if (is_opening) popover.showPopover();
window.requestAnimationFrame(() => {
let animation = popover.animate(
{
translate,
opacity,
},
{
duration: 300,
easing: 'ease-in-out',
fill: 'forwards',
},
);
animation.onfinish = () => {
if (!is_opening) popover.hidePopover();
};
});
}
// In Manual mode, you need to trigger keyboard events yourself
document.addEventListener('keydown', (event) => {
if (
event.key === 'Escape' &&
popover.hasAttribute('popover') &&
popover.matches(':popover-open')
) {
toggle();
}
});
</script>
<button id="md" class="menu-button"><img src="/menu.svg" /></button>
<!-- Note: Manual Mode Required -->
<div popover="manual" id="mdp" class="menu">
<ul class="menu-demo">
<li><a href="#">Settings</a></li>
<li><a href="#">My Profile</a></li>
<li><a href="#">Help</a></li>
<li><a href="#">Logout</a></li>
</ul>
</div>
Menu w/ Anchor Positioning
Here’s where things get interesting. This uses a few crazy new APIs, popover, anchor, @starting-style, allow-discrete. Basically a who’s who of unsupported cool stuff. On top of that anchor is still greatly in flux.
Disclaimer - This demo may or may not work. 🤷‍♂️
<button id="menu-anchor" class="menu-button" popovertarget="anchored-menu">
<img src="/menu.svg" />
</button>
<div popover id="anchored-menu" class="menu">
<ul class="menu-demo">
<li><a href="#">Settings</a></li>
<li><a href="#">My Profile</a></li>
<li><a href="#">Help</a></li>
<li><a href="#">Logout</a></li>
</ul>
</div>
<style>
#menu-anchor {
anchor-name: --menu;
}
#anchored-menu[popover] {
transition:
display 0.3s allow-discrete,
opacity 0.3s,
translate 0.3s;
transition-timing-function: ease-in;
opacity: 0;
translate: 0 30px;
position: absolute;
inset: unset;
top: anchor(--menu bottom);
left: anchor(--menu left);
}
#anchored-menu[popover]:popover-open {
opacity: 1;
translate: 0 0;
transition-timing-function: ease-out;
}
@starting-style {
#anchored-menu[popover]:popover-open {
opacity: 0;
translate: 0 30px;
}
}
</style>
Side note - Anchor Positioning
Anchor positioning is a fix for elements placed in the top-layer where relative context doesn’t exist. This is how you pin a menu to a location. It’s great, but not only does support suck, the API itself is not agreed upon and still in flux. See: Anchor Position Issues
Can I use anchor positioning? - âť” Big No 48% support
But wait?! Is there a Polyfill?
There is, Anchor Polyfill but, it’s not currently current to the spec, and the spec isn’t final, so hold off for now.