HTML Nav Element
Every website has a few important routes: "Home", "Products", "About", "Contact". The <nav> element is how you tell the browser, "this block of links is for finding your way around." Screen reader users can then jump straight to it, or skip past it, in a single keystroke.
The trick with <nav> is knowing when not to use it. Not every list of links is navigation, and too many <nav> regions make a page harder to use, not easier. This lesson shows the right way to build menus, breadcrumbs, and mobile buttons, and the wrong ways to avoid.
What You Will Learnβ
- What
<nav>means, and which links deserve it - Building a menu with a list inside
<nav> - Naming several navigation regions with
aria-label - Showing the current page with
aria-current - Breadcrumbs and pagination
- An accessible mobile menu button with
aria-expanded - Dropdown menus, and why to avoid the ARIA
menurole - Accessibility, security, and global notes
The Basicsβ
- HTML Code
- Browser Output
<nav aria-label="Main">
<ul>
<li><a href="/">Home</a></li>
<li><a href="/programme">Programme</a></li>
<li><a href="/tickets">Tickets</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
</nav>
nav ul {
display: flex;
gap: 1rem;
list-style: none;
margin: 0;
padding: 0;
}
nav a {
display: inline-block;
padding: 0.5rem 0.75rem;
}
Three things are happening in that small snippet:
<nav>marks the block as a navigation landmark.<ul>and<li>list the links. A screen reader announces "list, 4 items," so people know how long the menu is before they start.aria-label="Main"names the landmark. A screen reader says "Main navigation." (Do not write "Main navigation" in the label, or it is read as "Main navigation navigation.")
Like <header>, <nav> has no default look beyond being a block. The row of links is all CSS.
Which Links Deserve a <nav>?β
The rule: major blocks of navigation links. The specification itself says that not all groups of links need to be in a <nav>.
Use <nav> for | Do not use <nav> for |
|---|---|
| The main site menu | A few links inside a paragraph |
| A secondary menu (account, language) | A list of "related articles" (use <aside> or a <section>) |
| Breadcrumbs | Social-media icons in a footer (a plain list is enough) |
| A table of contents for the page | Links to legal pages in the footer, if there are only two or three |
| Pagination ("Previous", "1 2 3", "Next") | Any random group of links |
| A section menu in a documentation site | A set of buttons that do actions (they are not links) |
Why the restraint? Every <nav> becomes an entry in the screen reader's landmark list. If a page has ten of them, the list is noise. Most pages need between one and four.
Ask: "Would a visitor who is lost look here to find their way?" If yes, <nav>. If the links are only "also interesting," it is not navigation.
Several Navs: Give Each a Nameβ
When there is more than one <nav>, a screen reader says just "navigation" for each, and the user cannot tell them apart. Add an aria-label (or point to a visible heading with aria-labelledby):
<header>
<nav aria-label="Main">β¦</nav>
</header>
<main>
<nav aria-label="Breadcrumb">β¦</nav>
β¦
</main>
<footer>
<nav aria-label="Footer">β¦</nav>
</footer>
| Label | Read aloud as |
|---|---|
aria-label="Main" | "Main navigation" |
aria-label="Breadcrumb" | "Breadcrumb navigation" |
aria-label="Footer" | "Footer navigation" |
aria-label="Pagination" | "Pagination navigation" |
Each label should be unique on the page, short, and written in the page's language. A page with a single <nav> does not strictly need a label, but it does no harm.
Showing the Current Pageβ
Visitors need to know where they are. Mark the link to the current page with aria-current="page":
<nav aria-label="Main">
<ul>
<li><a href="/">Home</a></li>
<li><a href="/programme">Programme</a></li>
<li><a href="/tickets" aria-current="page">Tickets</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
</nav>
A screen reader says "Tickets, current page, link." You can also style it with the attribute selector, so the visual and the spoken version always agree:
nav a[aria-current="page"] {
font-weight: 700;
border-bottom: 3px solid #1d4ed8; /* not colour alone */
}
Try It: Move Aroundβ
Click a link. The "current" mark moves, and the markup changes with it.
- HTML Code
- Browser Output
<nav aria-label="Main">
<ul>
<li><a href="/" aria-current="page">Home</a></li>
<li><a href="/programme">Programme</a></li>
<li><a href="/tickets">Tickets</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
</nav>
// In a real site, your server or framework adds aria-current when it builds
// each page. This sketch shows the idea for a single-page script.
const links = document.querySelectorAll("nav a");
links.forEach((link) => {
link.addEventListener("click", (event) => {
event.preventDefault();
links.forEach((other) => other.removeAttribute("aria-current"));
link.setAttribute("aria-current", "page");
});
});
Home
Welcome to the festival. Doors open at 9:00.
The link for this page has: <a href="/home" aria-current="page">
Other aria-current values exist (step for a progress trail, location, date, true), but page is the one you will use most.
Breadcrumbsβ
Breadcrumbs show the path from the home page to the current page. Use an ordered list, because the order matters, and a clear name:
<nav aria-label="Breadcrumb">
<ol>
<li><a href="/">Home</a></li>
<li><a href="/shop">Shop</a></li>
<li><a href="/shop/bread">Bread</a></li>
<li><a href="/shop/bread/sourdough" aria-current="page">Sourdough</a></li>
</ol>
</nav>
nav[aria-label="Breadcrumb"] ol {
display: flex;
flex-wrap: wrap;
gap: 0.25rem;
list-style: none;
margin: 0;
padding: 0;
}
/* The separators are decoration, so CSS draws them */
nav[aria-label="Breadcrumb"] li + li::before {
content: "/";
margin-inline-end: 0.25rem;
color: #64748b;
}
Drawing the separator with CSS keeps / or > out of what a screen reader reads.
Paginationβ
<nav aria-label="Pagination">
<ul>
<li><a href="/blog?page=1" rel="prev">Previous</a></li>
<li><a href="/blog?page=1">1</a></li>
<li><a href="/blog?page=2" aria-current="page">2</a></li>
<li><a href="/blog?page=3">3</a></li>
<li><a href="/blog?page=3" rel="next">Next</a></li>
</ul>
</nav>
Give the numbers clear text, such as "Page 3" (you can hide the word "Page" visually), so a screen reader user hearing just "3" is not left guessing.
A Mobile Menu Buttonβ
On a small screen, a long menu is often hidden behind a "Menu" button. This is a disclosure pattern: a button that shows or hides a block. The accessible version needs a real <button> and two attributes:
| Attribute | Job |
|---|---|
aria-expanded="false" or "true" | Tells assistive technology whether the menu is open |
aria-controls="menu-list" | Points to the id of the list the button controls |
Try it. Open the menu, then press Esc. Focus goes back to the button.
- HTML Code
- Browser Output
<nav aria-label="Main">
<button type="button" aria-expanded="false" aria-controls="menu-list" id="menu-button">
Menu
</button>
<ul id="menu-list" hidden>
<li><a href="/programme">Programme</a></li>
<li><a href="/tickets">Tickets</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
</nav>
const button = document.getElementById("menu-button");
const list = document.getElementById("menu-list");
function setOpen(open) {
button.setAttribute("aria-expanded", String(open));
button.textContent = open ? "Close menu" : "Menu";
list.hidden = !open;
}
button.addEventListener("click", () => {
setOpen(button.getAttribute("aria-expanded") !== "true");
});
// Esc closes the menu and returns focus to the button
document.addEventListener("keydown", (event) => {
if (event.key === "Escape" && button.getAttribute("aria-expanded") === "true") {
setOpen(false);
button.focus();
}
});
The button currently has aria-expanded="false". Open the menu, then press Esc.
Notes:
- The hidden attribute removes the list from view and from the accessibility tree, so a screen reader skips it while it is closed.
- Keep the button's name consistent, or update
aria-expandedand leave the label alone. Do not do both in a way that reads "Close menu, expanded." - The button is a real
<button>so Enter and Space work with no extra code. See Why Semantic HTML. - A hamburger icon alone needs a text name: either visible text, or
aria-label="Menu"on the button. - Show the full menu on wide screens with CSS, and keep the button for small ones.
Dropdown Submenusβ
Many sites show a submenu when you hover over "Products." A mouse-only hover is not accessible: keyboard and touch users cannot open it. The safe pattern is the same disclosure button:
<li>
<button type="button" aria-expanded="false" aria-controls="products-menu">Products</button>
<ul id="products-menu" hidden>
<li><a href="/products/bread">Bread</a></li>
<li><a href="/products/cakes">Cakes</a></li>
</ul>
</li>
Avoid the ARIA menu, menubar, and menuitem roles for website navigation. They promise the keyboard behaviour of an application menu (arrow keys, type-ahead), which you would have to build completely, and they change how screen readers treat the links. A plain list of links and disclosure buttons is simpler and more reliable. Also remember:
- Do not open on hover only. Offer a click, tap, or Enter key.
- Keep submenus shallow. Two levels is plenty, and one is better.
- Make the parent a button or link with a real purpose, and not a link that goes nowhere.
Where to Put <nav>β
It does not have to be inside <header>. Common places:
| Place | Typical use |
|---|---|
In the page <header> | Main menu |
At the top of <main> | Breadcrumbs |
Inside <aside> or beside the content | A section menu or table of contents |
In the <footer> | A secondary or sitemap-style menu |
| After the content | Pagination, "previous and next" links |
Accessibility at a Glanceβ
- Use
<nav>for major link blocks only, typically one to four per page. - Use a list (
ulorol) inside, so the number of links is announced. - Name each
<nav>with a unique, shortaria-labelwhen there is more than one. - Mark the current page with
aria-current="page", and do not rely on colour alone. - Use real links (
<a href>) for navigation, and real buttons for opening and closing menus. - Keep keyboard order the same as the visual order.
- Provide a visible focus style, and large enough touch targets (at least 24 by 24 CSS pixels, ideally 44).
- Do not open menus on hover only, and let Esc close them.
- Keep the same menu in the same place on every page (consistent navigation is a WCAG rule).
- Add a skip link before the header, so keyboard users can bypass a long menu. See Header.
Security and Privacy Notesβ
- Open links to other sites safely. For links that open in a new tab,
rel="noopener"is applied by modern browsers fortarget="_blank", but addingrel="noopener noreferrer"is still good practice for untrusted destinations. - Never build menu links from untrusted input (such as a
?next=parameter), or attackers can send visitors to fake sites. Allow only your own paths. - Do not link to admin pages from public menus. Hiding a link does not protect a page; always check permissions on the server.
- Escape dynamic labels, such as a user's name in an account menu.
- Do not put private data in link addresses, such as session tokens or personal IDs. See Links and Navigation.
Global Audience Notesβ
- Set
langfor the page, and name regions in the page's language:aria-label="Principal"on a French page. - Labels get longer in translation. Let the menu wrap, or collapse into a button, instead of overflowing.
- Right-to-left languages: with
dir="rtl", flex menus mirror on their own. Use logical CSS properties (margin-inline-end) for separators and spacing, and mirror arrows and "next" and "previous" icons. - Do not rely on icons alone. A house means "Home" in many places, but not everywhere. Add text.
- Keep menu text short and plain, so it translates well and fits small screens.
- Provide a language switcher as a
<nav aria-label="Language">, with each language name written in its own language and marked withlang. - Slow connections: a menu that works as plain links without JavaScript is the most resilient.
Common Mistakes to Avoidβ
- Wrapping every group of links in
<nav>. - Several
<nav>elements with no labels, so they are indistinguishable. - Writing "navigation" inside the label ("Main navigation navigation").
- No list inside the nav, so the number of links is not announced.
- No indication of the current page, or colour as the only indication.
- Using
<div onclick>instead of real links. - A hamburger icon with no text name, and no
aria-expanded. - Menus that open on hover only.
- Using ARIA
menuroles for ordinary website menus. - Hiding the menu with
display: noneon desktop by mistake, leaving no way in for keyboard users. - Different menu order or position on different pages.
- Putting buttons that perform actions (such as "Delete") inside
<nav>.
Practice Timeβ
Try these in your editor:
- Build a main menu with
<nav>, a<ul>, and four links, and style it as a row. - Add
aria-label="Main", and check how the browser's accessibility panel names it. - Mark one link with
aria-current="page", and style it using the attribute selector. - Add a breadcrumb
<nav>with an ordered list, and draw the separators with CSS. - Add a pagination
<nav>with "Previous", numbers, and "Next". - Make a "Menu" button with
aria-expandedandaria-controlsthat shows and hides the list. - Add the Esc key to close the menu and return focus to the button.
- Turn the menu into a row on wide screens with a media query, and hide the button there.
- Tab through the page. Is the focus always visible, and the order logical?
- Bonus: count the
<nav>elements on a site you use. Does each have a clear name, and are there too many?
Quick Recapβ
Click a question to check your answer.
1. What does the <nav> element do?
It marks a block of major navigation links as a navigation landmark, so assistive technology users can jump to it or skip past it.
2. Should every group of links be wrapped in <nav>?
No. Use it for major navigation: the main menu, breadcrumbs, pagination, a table of contents. Too many <nav> regions make the landmark list noisy.
3. How do you tell apart several <nav> elements?
Give each a unique, short aria-label (such as "Main", "Breadcrumb", or "Footer"), or link it to a visible heading with aria-labelledby. Do not add the word "navigation" to the label.
4. How do you show the current page in a menu?
Add aria-current="page" to that link, and style it with a[aria-current="page"], using more than colour alone.
5. What does an accessible mobile menu button need?
A real <button>, aria-expanded that switches between false and true, aria-controls pointing at the list, a text name, and Esc to close with focus returned to the button.
6. Why avoid the ARIA menu role for website navigation?
It promises application-menu keyboard behaviour (arrow keys, type-ahead) that you would have to build, and it changes how screen readers treat the links. A list of links with disclosure buttons is simpler and more reliable.
Key Takeawaysβ
<nav>marks major navigation blocks as landmarks. Most pages need only one to four.- Put the links in a list (
ul, orolfor breadcrumbs), so the count is announced. - Give each
<nav>a unique shortaria-labelwhen there is more than one. - Show the current page with
aria-current="page", styled with more than colour. - Use a real
<button>witharia-expandedandaria-controlsfor mobile menus and submenus, and make sure Esc and keyboard users are covered. - Never rely on hover alone, and avoid ARIA
menuroles for website menus. - Keep navigation consistent from page to page, and plan for longer labels, RTL layouts, and text alternatives for icons.
- A
<nav>is for links that help people find their way, not for every list of links.
Next, learn the container for the page's real content, in Main, or return to the Semantic HTML overview.