HTML Navigation Menus

A navigation menu is the group of links a website uses to move visitors between pages: a header bar, a sidebar, a footer sitemap, or breadcrumbs. HTML doesn’t have a special “menu widget” tag the way some UI toolkits do — instead, navigation menus are built from ordinary elements (nav, ul, li, and a) arranged in a meaningful structure. Getting that structure right matters enormously: it’s how screen readers announce “navigation, 5 items” instead of reading a wall of disconnected text, and it’s how search engines understand your site’s layout.

This lesson covers how to mark up navigation menus correctly, from a simple header bar to multi-level nested menus, and the mistakes that quietly break accessibility even when the page still looks fine visually.

Overview / How it works

Semantically, a navigation menu is just a list of links. HTML gives us a purpose-built landmark element, <nav>, to say “the links inside this box are a navigation block, not just page content.” Inside a <nav>, the conventional way to group the links is an unordered list (<ul>) of list items (<li>), each containing one <a> link. That’s not a hard rule enforced by the browser — a nav could technically contain a single paragraph of links — but the list structure is the widely accepted convention because a menu genuinely is a list of destinations, and lists communicate “here are N related items” to assistive technology automatically.

When the browser parses a menu like this, it builds a subtree in the DOM: a nav node containing a ul node, containing several li nodes, each containing an a node. By default, browsers render nav as an ordinary block-level container with no special visual styling — it doesn’t look like a “menu” until CSS is applied (spacing links horizontally, removing bullet points, adding hover states, and so on is the job of the CSS course, not HTML). What HTML controls is the meaning: the accessibility tree exposes nav as a navigation landmark, and screen reader users can jump directly to it (or skip past it) using landmark navigation shortcuts. A ul is exposed as a list with a known item count, and each li > a pair becomes a focusable, keyboard-reachable link. None of that semantic value exists if you build the same visual layout out of generic <div> elements instead.

A page can have more than one nav — a main header menu, a footer sitemap, a sidebar table of contents — but not every group of links needs to be a nav. Reserve it for major, reusable blocks of navigation links; a couple of inline links inside an article don’t need to be wrapped in nav.

Syntax

<nav aria-label="Primary">
  <ul>
    <li><a href="/">Home</a></li>
    <li><a href="/about">About</a></li>
    <li><a href="/contact">Contact</a></li>
  </ul>
</nav>
  • nav — the landmark wrapper; tells browsers and assistive tech that this block is navigation.
  • aria-label (on nav) — a short accessible name for the landmark, essential when a page has more than one nav so users can tell “Primary” navigation apart from “Footer” navigation.
  • ul / li — the list structure; every navigable destination is one li.
  • a href — the actual link; href is what makes it keyboard-focusable and clickable. Without a valid href, an <a> is not a link at all.
  • aria-current="page" (on the active link’s a) — optional but recommended; marks which link represents the page the user is currently on.

Examples

Example 1: A simple horizontal menu

<nav aria-label="Primary">
  <ul>
    <li><a href="/">Home</a></li>
    <li><a href="/products">Products</a></li>
    <li><a href="/pricing">Pricing</a></li>
    <li><a href="/contact">Contact</a></li>
  </ul>
</nav>

Result: The browser renders a bulleted list of four links stacked vertically (bullets and vertical stacking are the default ul/li styling — turning this into a horizontal bar is done with CSS in a later course). Behind the scenes, the accessibility tree exposes a navigation landmark named “Primary” containing a list of 4 items, so a screen reader user hears “navigation, Primary, list, 4 items” and can tab through Home, Products, Pricing, and Contact in order.

Example 2: Marking the current page

<nav aria-label="Primary">
  <ul>
    <li><a href="/">Home</a></li>
    <li><a href="/products" aria-current="page">Products</a></li>
    <li><a href="/pricing">Pricing</a></li>
  </ul>
</nav>

Result: Visually nothing changes by default — aria-current carries no built-in styling. What changes is announced state: assistive technology reports the “Products” link as the current page, and this hook is also commonly targeted by CSS ([aria-current="page"]) to visually highlight the active tab. This is the correct, semantic way to say “you are here,” rather than relying on a color change alone.

Example 3: A two-level (nested) menu

<nav aria-label="Primary">
  <ul>
    <li><a href="/">Home</a></li>
    <li>
      <a href="/products">Products</a>
      <ul>
        <li><a href="/products/laptops">Laptops</a></li>
        <li><a href="/products/phones">Phones</a></li>
      </ul>
    </li>
    <li><a href="/contact">Contact</a></li>
  </ul>
</nav>

Result: The browser renders a nested bulleted list: Home, Products (with an indented sub-list of Laptops and Phones under it), then Contact. This is the plain, unstyled version of what becomes a “dropdown” menu once CSS hides/shows the inner list on hover or click; the HTML alone already expresses the correct hierarchy — Laptops and Phones are structurally children of Products.

How it works step by step

When the parser hits <nav>, it opens a landmark node in the DOM. Everything up to the matching </nav> becomes its descendants. The <ul> inside triggers list-construction rules: the parser expects only <li> children (any other direct child is treated as parser error recovery territory), and each <li> is auto-closed when the next <li> or the closing </ul> is encountered — that’s why you’ll sometimes see “working” menus even with missing </li> tags, though you should never rely on that. Each <a href="..."> becomes an inline-level, focusable element inserted into the page’s tab order in source order — this is why the order of your li elements is also the order a keyboard user tabs through them, regardless of how CSS later repositions them visually.

Common Mistakes

Mistake 1: Nesting a link inside another link

Anchors cannot contain other anchors. Browsers will silently “fix” this by closing the outer link early, producing a DOM structure that doesn’t match what you wrote — a frequent source of menu items that click through to the wrong page.

<nav>
  <a href="/">Home
    <a href="/about">About</a>
  </a>
</nav>

Corrected — keep links as siblings, never nested:

<nav>
  <a href="/">Home</a>
  <a href="/about">About</a>
</nav>

Mistake 2: List items with no surrounding list

Dropping <li> elements straight inside <nav> without a <ul> or <ol> wrapper is invalid — li is only valid as a direct child of a list element, and without it, assistive tech has no “list of N items” grouping to announce.

<nav>
  <li><a href="/">Home</a></li>
  <li><a href="/about">About</a></li>
</nav>

Corrected — wrap the items in a list:

<nav>
  <ul>
    <li><a href="/">Home</a></li>
    <li><a href="/about">About</a></li>
  </ul>
</nav>

Best Practices

  • Use <nav> for major navigation blocks (header menu, footer sitemap, sidebar), not for every small cluster of links on a page.
  • When a page has more than one <nav>, give each one an aria-label (e.g. “Primary”, “Footer”) so they’re distinguishable in the accessibility tree.
  • Structure the links as a ul/li list — it’s the clearest way to communicate “a group of N related destinations.”
  • Mark the active page’s link with aria-current="page" instead of relying purely on a visual color change.
  • Keep href values meaningful and working; never use an empty href="#" as a placeholder for a menu item that does nothing.
  • Reserve visual layout (horizontal bars, dropdowns, hover effects, hamburger icons) for CSS — HTML should describe structure and meaning only.
  • Don’t nest interactive elements (a link inside a link, a button inside a link); keep every list item’s link as a single, unambiguous target.

Practice Exercises

1. Build a nav labeled “Primary” containing a ul of four links: Home, Blog, Services, Contact.

2. Take the menu from exercise 1 and add aria-current="page" to the “Blog” link, as if the user is currently viewing the blog page.

3. Build a footer nav labeled “Footer” with a two-level nested list: a top-level “Resources” item containing a nested list of “Docs” and “FAQ” links, alongside two top-level sibling links, “Privacy” and “Terms”.

Summary

  • Navigation menus are built from ordinary elements — nav, ul, li, and a — not a special “menu” tag.
  • <nav> marks a block as a navigation landmark, which assistive technology and search engines rely on.
  • Wrapping links in a ul/li list communicates a countable group of related destinations.
  • Use aria-label to distinguish multiple nav elements on the same page.
  • Use aria-current="page" to mark the active link semantically.
  • Never nest an a inside another a, and never place li outside of a ul/ol.
  • Visual menu styling (horizontal layout, dropdowns, highlighting) belongs to CSS, not HTML.