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.

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:

  • nav is a landmark. Screen reader users jump straight to landmarks, so a real nav is the difference between finding the menu instantly and hunting for it.
  • ul and li tell the reader how many items there are before they start moving through them. A screen reader announces list, six items.
  • a href makes 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.

<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.

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-expanded must be flipped between true and false by the script that opens and closes the menu. It is the only thing that tells a screen reader the state changed.
  • aria-controls names 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

  • nav is for major navigation blocks. Not every group of links needs one - a paragraph full of links does not.
  • A ul may contain only li elements as direct children. The links go inside the list items.
  • Do not put a nested list inside the ul; put it inside the li it 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 nav if it is a short list of secondary links, but naming it does no harm.

Common mistakes

  • A row of div elements 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 nav elements with no labels.
  • Using a link as the dropdown trigger, so Space scrolls 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 nav and mark the current page with aria-current.
  • Make submenus work by click, and make them closable with Escape.
  • Use hidden or display: none for closed menus so their links leave the tab order.
  • Add breadcrumb structured data on deep pages so search results show the path.

Practice

  1. Build a five item main menu with the current page marked, then tab through it with the mouse untouched.
  2. Add a breadcrumb trail using an ordered list, with the separators generated in CSS.
  3. Build a dropdown whose trigger is a button, and toggle aria-expanded correctly when it opens and closes.
  4. Give a page a main, a breadcrumb and a footer navigation, then list the landmarks in the DevTools accessibility panel.

Useful resources

Hand picked references for this topic
Written by Lorens Mishra

Software Engineer Notes Management System Administrator

Continue reading

All HTML notes →

Discussion

0 comments
Sign in to join the discussion.

No comments yet. Be the first to say something.