Building a Navigation Menu
A menu is a list of links inside a nav element. Learn the markup that makes it accessible, how to mark the current page, and how a dropdown should be built.
-
HTML Basics
- What is HTML: The Structure Layer of Every Web Page
- HTML Document Structure: DOCTYPE, html, head and body
- Elements, Tags and Attributes: The Vocabulary of HTML
- HTML Comments: Notes That Ship With Your Code
- Block Level and Inline Elements
- Writing and Running Your First HTML Page
- How a Browser Turns Markup Into a Page
- Text and Formatting
- Links and Navigation
- Images and Media
- Lists
- Tables
-
Forms
- Form Structure: form, action and method
- Input Types: Text, Email, Number, Date and the Rest
- Labels: The Most Important Element in a Form
- Checkboxes, Radio Buttons and Grouping
- select, option, optgroup and datalist
- textarea, File Uploads and Hidden Fields
- Buttons: submit, reset and button
- Built In Form Validation
- GET or POST: What Happens When a Form Is Submitted
- Semantic HTML
- HTML5 Features
- Head and Metadata
- HTML with CSS
- HTML with JavaScript
- Accessibility
-
HTML SEO
- How Google Works: Crawling, Indexing and Ranking
- SEO Friendly HTML Structure
- Titles and Descriptions That Earn Clicks
- Headings and Content Structure for Search
- Internal Linking and Anchor Text
- robots.txt and XML Sitemaps
- Canonical URLs and Duplicate Content
- Structured Data and JSON-LD
- Image SEO
- Core Web Vitals and Mobile Friendliness
- DevTools and Debugging
- Editor Productivity
- HTML Best Practices
- HTML Projects
- Advanced Projects
- Practice and Exams
Concept
Site navigation is a list of links wrapped in a nav element. That is the whole pattern, and it is worth being precise about why each part is there:
navis a landmark. Screen reader users jump straight to landmarks, so a realnavis the difference between finding the menu instantly and hunting for it.ulandlitell the reader how many items there are before they start moving through them. A screen reader announces list, six items.a hrefmakes each item a real link, so it is focusable, followable and crawlable.
Syntax
<nav aria-label="Main">
<ul>
<li><a href="/">Home</a></li>
<li><a href="/courses">Courses</a></li>
<li><a href="/admission">Admission</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
</nav>The bullets and the vertical stacking are browser defaults; CSS removes them. The list is there for the structure, not for the dots.
Marking the current page
A reader needs to know where they are, and colour alone does not tell a screen reader anything.
<li><a href="/courses" aria-current="page">Courses</a></li>aria-current="page" is announced as current page and gives CSS a hook that needs no extra class:
nav a[aria-current="page"] {
font-weight: 600;
border-bottom: 2px solid currentColor;
}Some designs remove the link entirely on the current page. That is defensible - the link goes nowhere useful - but keeping it linked with aria-current is simpler and lets a reader reload the section.
More than one nav
A page may have several. When it does, each needs a name, or a screen reader announces three identical navigation landmarks.
<nav aria-label="Main">...</nav>
<nav aria-label="Breadcrumb">...</nav>
<nav aria-label="Footer">...</nav>Do not include the word navigation in the label. The role is already announced, so aria-label="Main navigation" is read as main navigation navigation.
Breadcrumbs
<nav aria-label="Breadcrumb">
<ol>
<li><a href="/">Home</a></li>
<li><a href="/courses">Courses</a></li>
<li><a href="/courses/design" aria-current="page">Design</a></li>
</ol>
</nav>An ordered list, because the sequence is the point. Separators between the items belong in CSS, generated with ::before, so they are never read out as content.
Dropdown menus
A submenu is a nested list. The control that opens it is a button, not a link, because it performs an action rather than navigating.
<nav aria-label="Main">
<ul>
<li><a href="/">Home</a></li>
<li>
<button type="button" aria-expanded="false" aria-controls="courses-menu">
Courses
</button>
<ul id="courses-menu" hidden>
<li><a href="/courses/design">Design</a></li>
<li><a href="/courses/data">Data science</a></li>
</ul>
</li>
<li><a href="/contact">Contact</a></li>
</ul>
</nav>Three requirements make this work for everyone:
aria-expandedmust be flipped betweentrueandfalseby the script that opens and closes the menu. It is the only thing that tells a screen reader the state changed.aria-controlsnames the element being toggled.- The submenu must open on click or on
Enter, not on hover alone. A hover only menu is unreachable by keyboard and awkward on touch.
The mobile menu button
<button type="button"
class="menu-toggle"
aria-expanded="false"
aria-controls="site-menu">
<span class="hamburger" aria-hidden="true"></span>
Menu
</button>
<nav id="site-menu" aria-label="Main">
<ul>...</ul>
</nav>The three bar icon is decorative, so it is hidden from assistive technology and the button carries a real text label. A button with no accessible name is announced as just button, which tells the reader nothing.
Important rules
navis for major navigation blocks. Not every group of links needs one - a paragraph full of links does not.- A
ulmay contain onlylielements as direct children. The links go inside the list items. - Do not put a nested list inside the
ul; put it inside theliit belongs to. - Tab order follows markup order, so the source order of the menu is the order a keyboard reaches it.
- The footer navigation does not need a
navif it is a short list of secondary links, but naming it does no harm.
Common mistakes
- A row of
divelements with click handlers instead of a list of links. Nothing is focusable and nothing is crawlable. - Separators typed as text between links, so a screen reader reads Home slash Courses slash Contact.
- Several
navelements with no labels. - Using a link as the dropdown trigger, so
Spacescrolls the page instead of opening the menu. - Leaving
aria-expanded="false"permanently in the markup while the script toggles a class. - Hiding the menu with CSS only, leaving every link in the tab order while invisible.
Best practices
- Keep the main menu to around seven items. Longer lists belong in a footer or a sitemap page.
- Put the navigation early in the body, after the skip link.
- Label every
navand mark the current page witharia-current. - Make submenus work by click, and make them closable with
Escape. - Use
hiddenordisplay: nonefor closed menus so their links leave the tab order. - Add breadcrumb structured data on deep pages so search results show the path.
Practice
- Build a five item main menu with the current page marked, then tab through it with the mouse untouched.
- Add a breadcrumb trail using an ordered list, with the separators generated in CSS.
- Build a dropdown whose trigger is a button, and toggle
aria-expandedcorrectly when it opens and closes. - Give a page a main, a breadcrumb and a footer navigation, then list the landmarks in the DevTools accessibility panel.