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>