How to Add a Navigation Menu in Astro
Build a Header component with links, mark the page you're on, and add a phone menu that opens and closes with no JavaScript. One file, about 60 lines.
Tested on Astro 7.4

In this guide
A menu in Astro is one component. You list your links once. Astro marks the page you’re on. A <details> tag gives phones a menu that opens and closes, with no JavaScript. You put the component in your layout, and every page gets it.
Step 1: Make the Header component
Create src/components/Header.astro. Paste in all of this:
---
const links = [
{ href: '/', label: 'Home' },
{ href: '/about/', label: 'About' },
{ href: '/blog/', label: 'Blog' },
];
const here = Astro.url.pathname.replace(/\/$/, '') || '/';
function isCurrent(href: string) {
const target = href.replace(/\/$/, '') || '/';
if (target === '/') return here === '/';
return here === target || here.startsWith(target + '/');
}
---
<header>
<a href="/" class="logo">My Site</a>
<nav class="wide">
{links.map((link) => (
<a href={link.href} aria-current={isCurrent(link.href) ? 'page' : undefined}>
{link.label}
</a>
))}
</nav>
<details class="narrow">
<summary>Menu</summary>
<nav>
{links.map((link) => (
<a href={link.href} aria-current={isCurrent(link.href) ? 'page' : undefined}>
{link.label}
</a>
))}
</nav>
</details>
</header>
<style>
header {
display: flex;
justify-content: space-between;
align-items: flex-start;
padding: 1rem;
}
nav a {
margin-left: 1rem;
text-decoration: none;
}
nav a[aria-current='page'] {
font-weight: bold;
text-decoration: underline;
}
.narrow {
display: none;
}
@media (max-width: 600px) {
.wide {
display: none;
}
.narrow {
display: block;
}
.narrow nav a {
display: block;
margin: 0.5rem 0;
}
}
</style>
Change the links to match your pages. Change My Site to your site’s name.
Step 2: Understand the three parts
You don’t need to learn all of it. Here’s the short tour.
The list. The links list at the top holds your menu. To add a page, add a line. The menu is built from this list, so you never write a link twice by hand.
The current page. Astro.url.pathname is the address of the page being built, like /about/. The isCurrent check compares it to each link. When they match, the link gets aria-current="page". Screen readers say “current page” out loud. The CSS makes it bold and underlined.
The check also counts pages inside a section. So on /blog/my-first-post/, the Blog link lights up too. The Home link only lights up on the home page.
The phone menu. The <details> tag is plain HTML. It shows the word in <summary>. Tap it and the rest opens. Tap again and it closes. The CSS shows the normal menu on wide screens. On screens under 600 pixels wide it shows the <details> menu instead.
Step 3: Add it to your layout
Open your layout, like src/layouts/Layout.astro. Import the header at the top, then put it as the first thing in <body>:
---
import Header from '../components/Header.astro';
---
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>My Site</title>
</head>
<body>
<Header />
<slot />
</body>
</html>
Keep anything else your layout already has in <head>. Just add the import and the <Header /> line.
Step 4: Build and check
Run npm run build. The end of the output lists your pages. Here is ours:
$ npm run build
19:31:29 ├─ /about/index.html (+4ms)
19:31:29 ├─ /blog/first-post/index.html (+2ms)
19:31:29 ├─ /blog/index.html (+1ms)
19:31:29 ├─ /index.html (+1ms)
19:31:29 [build] Complete!Then run npm run dev and click around. The link for the page you’re on is bold. Make your browser window narrow. The links turn into a “Menu” button. Tap it to open the list.
We ran this on a fresh Astro 7.4 project with four pages. Each page marked the right link, and /blog/first-post/ marked Blog. We also opened it at phone size. The menu opened and showed the current page in bold.
Building with Claude? Paste this guide into Claude Code and ask it to do it for you.
Make it better
- Close the menu after a tap. On a normal site, each link loads a new page, so the menu starts closed again. No extra code needed.
- Add a “skip to content” link. It helps people who use a keyboard jump past the menu.
- Style it your way. All the CSS is in the
<style>part. It only affects this component, so it won’t break the rest of your site.
Where to go next
- How to add dark mode to Astro. A light or dark button fits nicely in this header.
- How to add search to Astro, then add a Search link to your list.
- How to add view transitions to Astro for smooth moves between pages.
Frequently asked questions
How do I highlight the current page in an Astro menu?
Compare each link to Astro.url.pathname, which is the address of the page being built. When they match, add aria-current="page" to that link and style it with CSS.
Do I need JavaScript for a phone menu?
No. The HTML details element opens and closes on a tap by itself. This guide uses it, so the menu works even if JavaScript is off.
How do I add a new page to the menu?
Add one line to the links list at the top of Header.astro. Both the wide menu and the phone menu use that list, so they stay the same.
Stuck on a step?
Check the common errors guide, look up a word in the glossary, or ask the friendly people in the official Astro and EmDash Discords. How to ask so people can help.