Skip to content

Instantly share code, notes, and snippets.

@benhodgson87
Created March 7, 2025 11:03
Show Gist options
  • Save benhodgson87/7a61e18ba07e0d93578be283c57cbc25 to your computer and use it in GitHub Desktop.
Save benhodgson87/7a61e18ba07e0d93578be283c57cbc25 to your computer and use it in GitHub Desktop.
Astro Hamburger Menu
---
import { siteName } from "~/config/config";
const links = [
{
text: 'Home',
href: '/'
},
{
text: 'About',
href: '/'
},
{
text: 'Contact us',
href: '/'
}
];
---
<nav class="nav" data-menu>
<div class="nav-inner">
<header class="header">
<a class="title" href="/">{siteName}</a>
<button class="nav-menu-handle" data-menu-handle aria-label="Toggle menu">
<span class="nav-menu-icon">
<span class="layer"></span>
<span class="layer"></span>
<span class="layer"></span>
<span class="layer"></span>
</span>
</button>
</header>
<ul class="nav-menu-items">
{
links.map(({ text, href }) => (
<li class="nav-menu-item">
<a href={href}>{text}</a>
</li>
))
}
</ul>
</div>
</nav>
<style>
.nav {
display: flex;
flex-direction: column;
align-items: center;
padding: 0 0 0 var(--spacing-sm);
background-color: var(--color-bg-dark);
color: var(--color-text-body-on-dark);
}
.nav-inner {
width: 100%;
max-width: var(--dimension-max-content-width);
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0;
}
.title {
font-size: var(--typography-h1-font-size);
font-weight: 400;
line-height: var(--typography-h1-line-height);
}
.menu-icon {
width: 40px;
height: 40px;
color: #fff;
}
.nav-menu-handle {
margin: 0;
padding: var(--spacing-sm);
border: 0;
background-color: transparent;
cursor: pointer;
}
.nav-menu-icon,
.nav-menu-icon .layer {
pointer-events: none;
}
.nav-menu-icon {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 25px;
height: 25px;
-webkit-transform: rotate(0deg);
-moz-transform: rotate(0deg);
-o-transform: rotate(0deg);
transform: rotate(0deg);
-webkit-transition: .5s ease-in-out;
-moz-transition: .5s ease-in-out;
-o-transition: .5s ease-in-out;
transition: .5s ease-in-out;
}
.nav-menu-icon .layer {
display: block;
position: absolute;
height: 4px;
width: 100%;
background: var(--color-text-body-on-dark);
border-radius: 9px;
opacity: 1;
left: 0;
-webkit-transform: rotate(0deg);
-moz-transform: rotate(0deg);
-o-transform: rotate(0deg);
transform: rotate(0deg);
-webkit-transition: .25s ease-in-out;
-moz-transition: .25s ease-in-out;
-o-transition: .25s ease-in-out;
transition: .25s ease-in-out;
}
.nav-menu-icon .layer:nth-child(1) {
top: 0px;
}
.nav-menu-icon .layer:nth-child(2),
.nav-menu-icon .layer:nth-child(3) {
top: 10px;
}
.nav-menu-icon .layer:nth-child(4) {
top: 20px;
}
.nav[data-menu="true"] .nav-menu-icon .layer:nth-child(1) {
top: 10px;
width: 0%;
left: 50%;
}
.nav[data-menu="true"] .nav-menu-icon .layer:nth-child(2) {
-webkit-transform: rotate(45deg);
-moz-transform: rotate(45deg);
-o-transform: rotate(45deg);
transform: rotate(45deg);
}
.nav[data-menu="true"] .nav-menu-icon .layer:nth-child(3) {
-webkit-transform: rotate(-45deg);
-moz-transform: rotate(-45deg);
-o-transform: rotate(-45deg);
transform: rotate(-45deg);
}
.nav[data-menu="true"] .nav-menu-icon .layer:nth-child(4) {
top: 10px;
width: 0%;
left: 50%;
}
.nav-menu-items {
display: none;
list-style: none;
padding: 0;
margin: 0;
}
nav[data-menu="true"] .nav-menu-items {
display: block;
}
.nav-menu-item a {
display: block;
padding: var(--spacing-sm) var(--spacing-sm) var(--spacing-sm) 0;
color: var(--color-text-body-on-dark);
text-decoration: none;
}
@media (--breakpoint-tablet) {
.nav {
padding: 0 var(--spacing-md);
}
.header {
padding: 0;
}
.nav-inner {
display: inline-flex;
justify-content: space-between;
}
.nav-menu-items,
nav[data-menu="true"] .nav-menu-items {
display: inline-flex;
list-style: none;
}
.nav-menu-item {
margin-left: var(--spacing-md);
padding: 0;
}
.nav-menu-icon {
display: none;
}
}
</style>
<script>
const menuAttribute = 'data-menu';
const handleAttribute = 'data-menu-handle';
const toggleOrSetMenuState = (state?: boolean) => {
const menu = document.querySelector(`[${menuAttribute}]`);
if (!menu) return console.error(` [${menuAttribute}] not found in document`);
if (typeof state === 'boolean') {
return menu.setAttribute(menuAttribute, String(state));
}
return menu.setAttribute(
menuAttribute, String(menu.getAttribute(menuAttribute) === 'true' ? false : true)
);
}
const handleMenuButtonClick = (event: MouseEvent) => {
if ((event.target as HTMLElement).hasAttribute(handleAttribute)) {
return toggleOrSetMenuState();
}
}
const handleClickOutside = (event: MouseEvent) => {
const menu = document.querySelector(`[${menuAttribute}]`);
if (!menu) return console.error(`[${menuAttribute}] not found in document`);
if (!(event.target instanceof Node)) return;
if (!menu.contains(event.target) && menu.getAttribute(menuAttribute) === 'true') {
return toggleOrSetMenuState(false);
}
}
document.addEventListener("click", (event: MouseEvent) => {
handleMenuButtonClick(event);
handleClickOutside(event);
});
window.addEventListener("scroll", () => (window.scrollY > 50) && toggleOrSetMenuState(false));
window.addEventListener("resize", () => toggleOrSetMenuState(false));
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment