UI Element Mobile Nav
How to make a mobile nav with popover
using only browser APIs.
Mobile Nav with No JavaScript
This is a mobile nav using the popover API. The animation is only supported in Chrome currently because it uses @starting-style
and allow-discrete
. Hopefully these APIs drop across all browsers soon. Either way, check it out, it’s super simple and accessible, with no JavaScript.
<button popovertarget="demo-mobile-nav">NAV</button>
<nav popover id="demo-mobile-nav">
<button class="demo-button" popovertarget="demo-mobile-nav" popovertargetaction="hide">
Close Nav
</button>
<ul>
<li><a href="#">Home</a></li>
<li><a href="#">About</a></li>
<li><a href="#">Store</a></li>
<li><a href="#">Contact</a></li>
</ul>
</nav>
<style>
#demo-mobile-nav[popover] {
transition:
display 0.3s allow-discrete,
overlay 0.5s allow-discrete,
opacity 0.3s,
translate 0.3s;
transition-timing-function: ease-in;
translate: 100%;
margin: 0;
block-size: 100vb;
inline-size: 90vi;
inset-inline-start: unset;
inset-inline-end: 0;
&:popover-open {
translate: 0;
transition-timing-function: ease-out;
}
@starting-style {
&:popover-open {
translate: 100%;
}
}
}
</style>
Mobile Nav with Popover and View Transitions
<script>
const button = document.querySelector('button[popovertarget=demo-mobile-nav-vt]');
const popover = document.querySelector('#demo-mobile-nav-vt');
button.addEventListener('click', toggle);
function toggle(e) {
const is_opening = !popover.matches(':popover-open');
e.preventDefault();
document.startViewTransition(() => {
if (is_opening) {
return popover.showPopover();
}
return popover.hidePopover();
});
}
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && popover.hasAttribute('popover') && popover.matches(':popover-open')) {
toggle(e);
}
});
</script>
<button popovertarget="demo-mobile-nav-vt">NAV</button>
<nav popover id="demo-mobile-nav-vt">
<button class="demo-button" popovertarget="demo-mobile-nav-vt" popovertargetaction="hide">
Close Nav
</button>
<ul>
<li><a href="#">Home</a></li>
<li><a href="#">About</a></li>
<li><a href="#">Store</a></li>
<li><a href="#">Contact</a></li>
</ul>
</nav>
<style>
#demo-mobile-nav-vt[popover] {
margin: 0;
block-size: 100vb;
inline-size: 90vi;
inset-inline-start: unset;
inset-inline-end: 0;
view-transition-name: slide;
}
@keyframes slide {
from {
translate: 100vi;
}
}
::view-transition-old(slide) {
animation: 300ms ease-in reverse forwards slide;
}
::view-transition-new(slide) {
animation: 300ms ease-out forwards slide;
}
</style>
Mobile Nav with Popover and WAAPI
Back in reality this is where your front-end framework comes in however you can get a really great animated mobile nav with popover
combined with the Web Animations API
<script>
const button = document.querySelector('button[popovertarget=demo-mobile-nav-waapi]');
const close_button = document.querySelector('.close');
const popover = document.querySelector('#demo-mobile-nav-waapi');
button.addEventListener('click', toggle);
close_button.addEventListener('click', toggle);
function toggle(e) {
e.preventDefault();
const is_opening = !popover.matches(':popover-open');
const translate = is_opening ? ['100vi', '0'] : ['0', '100vi'];
if (is_opening) popover.showPopover();
window.requestAnimationFrame(() => {
let animation = popover.animate(
{
translate,
},
{
duration: 300,
easing: 'ease-in-out',
fill: 'forwards',
},
);
animation.onfinish = () => {
if (!is_opening) popover.hidePopover();
};
});
}
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && popover.hasAttribute('popover') && popover.matches(':popover-open')) {
toggle(e);
}
});
</script>
<button popovertarget="demo-mobile-nav-waapi">NAV</button>
<nav popover id="demo-mobile-nav-waapi">
<button class="demo-button close">Close Nav</button>
<ul>
<li><a href="#">Home</a></li>
<li><a href="#">About</a></li>
<li><a href="#">Store</a></li>
<li><a href="#">Contact</a></li>
</ul>
</nav>
<style>
#demo-mobile-nav-waapi[popover] {
margin: 0;
block-size: 100vb;
inline-size: 90vi;
inset-inline-start: unset;
inset-inline-end: 0;
}
</style>