This tutorial describes how to build an accessible main navigation of a website. You learn about semantic HTML, accessibility, and how using ARIA attributes can sometimes do more harm than good.


![Manuel Matuzović](https://web.dev/images/authors/mmatuzo.jpg) Manuel Matuzović [X](https://twitter.com/mmatuzo)

<br />

There are many different ways of building the main navigation of a website, in terms of styling, functionality, and the underlying markup and semantic information. If the implementation is too minimalist, it works for most people, but the user experience (UX) might not be great.
If it's over-engineered, it might confuse users or even hinder them from being able to access it at all.

For most websites, you want to build something that's neither too simple, nor too complicated.

## Building layer by layer

In this tutorial you start with a basic setup and add features layer by layer up to a point where you provide just enough information, styling, and functionality to please most users. To achieve that you make use of the progressive enhancement principle, which states that you start with the most fundamental and robust solution and progressively add layers of functionality. If one layer fails to work for some reason, the navigation will still work because it gracefully falls back to the underlying layer.

## Basic structure

For a basic navigation you need two things: `<a>` elements and a few lines of
CSS to improve the default styling and layout of your links.

- HTML

      <a href="/home">Home</a>
      <a href="/about-us">About us</a>
      <a href="/pricing">Pricing</a>
      <a href="/contact">Contact</a>

- CSS

      /* Define variables for your colors */
      :root {
        --color-shades-dark: rgb(25, 25, 25);
      }

      /* Use the alternative box model
      Details: <https://web.dev/learn/css/box-model/> */
      *{
        box-sizing: border-box;
      }

      /* Basic font styling */
      body {
        font-family: Segoe UI, system-ui, -apple-system, sans-serif;
        font-size: 1.6rem;
      }

      /* Link styling */
      a {
        --text-color: var(--color-shades-dark);
        border-block-end: 3px solid var(--border-color, transparent);
        color: var(--text-color);
        display: inline-block;
        margin-block-end: 0.5rem; /* See note at the bottom of this chapter */
        margin-inline-end: 0.5rem;
        padding: 0.1rem;
        text-decoration: none;
      }

      /* Change the border-color on :hover and :focus */
      a:where(:hover, :focus) {
        --border-color: var(--text-color);
      }

<iframe allow="camera; clipboard-read; clipboard-write; encrypted-media; geolocation; microphone; midi;" loading="lazy" src="https://codepen.io/web-dot-dev/embed/wvjvJrw?height=500&amp;theme-id=light&amp;default-tab=result&amp;editable=true" style="height: 100%; width: 100%; border: 0;" title="Pen wvjvJrw by web-dot-dev on Codepen"></iframe> View [Step 1: Basic HTML and CSS" on CodePen](https://codepen.io/web-dot-dev/pen/wvjvJrw).

This works well for most users, no matter how they're accessing the site. The navigation is accessible with a mouse, a keyboard, a touch device, or a screen reader, but there's room for improvement. You can enhance the experience by extending this basic pattern with additional functionality and information.

Here's what you can do:

- Highlight the active page.
- Announce the number of items to screen reader users.
- Add a landmark and allow screen reader users to access the navigation directly using a shortcut.
- Hide the navigation on narrow viewports.
- Improve focus styling.

> [!NOTE]
> **Note:** `margin-inline-end` and `margin-block-end` are known as [logical properties](https://web.dev/learn/css/logical-properties) and an alternative to writing `margin-right` and `margin-bottom`. Logical properties are a new way of working with directions and dimensions, one that allows you to control layout through logical, rather than physical mappings. This is especially useful, if you're dealing with websites that are presented in different languages and writing modes, like right-to-left. Have a look at ["Physical vs. logical properties" on CodePen](https://codepen.io/matuzo/pen/zYWmemQ?editors=1100) for an example.

## Highlight the active page

To highlight the active page, you can add a class to the corresponding link.

    <a href="/about-us" class="active-page">About us</a>

The issue with this approach is that it conveys the information which link is active purely visually. A blind screen reader user couldn't tell the difference between the active page and other pages. Fortunately, the [Accessible Rich Internet Applications (ARIA)](https://www.w3.org/TR/wai-aria/) standard offers a way to communicate this information semantically, as well. Use the [aria-current="page"](https://developer.mozilla.org/docs/Web/Accessibility/ARIA/Attributes/aria-current) attribute and value instead of a class.
> `aria-current` (state) indicates the element that represents the current item within a container or set of related elements. A page token used to indicate a link within a set of pagination links, where the link is visually styled to represent the currently-displayed page. \[Accessible Rich Internet Applications (WAI-ARIA) 1.1\](https://www.w3.org/TR/wai-aria/#aria-current)

With the additional attribute, a screen reader now announces something like "current page, link, About Us" instead of just "link, About Us".

    <a href="/about-us" aria-current="page" class="active-page">About us</a>

A convenient side effect is that you can use the attribute to select the active link in CSS, making the `active-page` class obsolete.

    <a href="/home">Home</a>
    <a href="/about-us" aria-current="page">About us</a>
    <a href="/pricing">Pricing</a>
    <a href="/contact">Contact</a>

    /* Change border-color and color for the active page */
    [aria-current="page"] {
      --border-color: var(--color-highlight);
      --text-color: var(--color-highlight);
    }

<iframe allow="camera; clipboard-read; clipboard-write; encrypted-media; geolocation; microphone; midi;" loading="lazy" src="https://codepen.io/web-dot-dev/embed/OJZJpOe?height=500&amp;theme-id=light&amp;default-tab=result&amp;editable=true" style="height: 100%; width: 100%; border: 0;" title="Pen OJZJpOe by web-dot-dev on Codepen"></iframe> View [Step 2: Highlight the active page on CodePen](https://codepen.io/web-dot-dev/pen/OJZJpOe).

## Announce the number of items

By looking at the navigation, sighted users can tell that it only contains four links. A blind screen reader user can't obtain this information as quickly. They may have to work their way through the whole list of links. This might not be an issue if the list is short like in this example, but if it contains 40 links this task can be cumbersome. If a screen reader user knows upfront that the navigation contains a lot of links, they might decide to use a different, more efficient way of navigation, like the site search.  

A nice way of communicating the number of items upfront, is wrapping each link in a list item (`<li>`), nested in an unordered list (`<ul>`).

    <ul>
      <li>
         <a href="/home">Home</a>
      </li>
      <li>
        <a href="/about-us" aria-current="page">About us</a>
      </li>
      <li>
        <a href="/pricing">Pricing</a>
      </li>
      <li>
        <a href="/contact">Contact</a>
      </li>
    </ul>

When a screen reader user finds the list, their software will announce something like "list, 4 items".

Here's a demo of the navigation used with the screen reader NVDA on Windows.

Now you have to adapt the styling to make it look like before.

    /* Remove the default list styling and create a flexible layout for the list */
    ul {
      display: flex;
      flex-wrap: wrap;
      gap: 1rem;
      list-style: none;
      margin: 0;
      padding: 0;
    }

    /* Basic link styling */
    a {
      --text-color: var(--color-shades-dark);

      border-block-end: 3px solid var(--border-color, transparent);
      color: var(--text-color);
      padding: 0.1rem;
      text-decoration: none;
    }

Using lists can have many advantages for screen reader users:

- They can get the total number of items before they interact with the items.
- They may use shortcuts to jump from list item to list item.
- They may use shortcuts to jump from list to list.
- The screen reader may announce the index of the current item (for example, "list item, two of four").

On top of that, if the page is presented without CSS, the list shows the links as a coherent group of items instead of just a pile of links.

A notable detail about VoiceOver in Safari is that you lose all these advantages, when you set `list-style: none`. This is by design. The WebKit team decided to [remove list semantics, when a list doesn't look like a list](https://bugs.webkit.org/show_bug.cgi?id=170179). Depending on the complexity of your navigation, this may or may not be an issue. On one hand, the navigation is still usable and it only affects VoiceOver in Safari. VoiceOver with Chrome or Firefox still announces the number of items, as well as other screen readers, like NVDA. On the other hand, the semantic information could be really useful in some situations. To make that decision, you should test the navigation with actual screen reader users and get their feedback. If you decide that you need VoiceOver in Safari to behave like all the other screen readers, you can work around the issue by setting the ARIA list role explicitly on the `<ul>`. This reverts the behaviour to the state before you've removed the list styling. Visually, the list still looks the same.

    <ul role="list">
      <li>
         <a href="/home">Home</a>
      </li>
      ...
    </ul>

> [!NOTE]
> **Note:** If you validate your HTML and the validation service reports a warning like "The list role is unnecessary for element ul", you can safely ignore that.

<iframe allow="camera; clipboard-read; clipboard-write; encrypted-media; geolocation; microphone; midi;" loading="lazy" src="https://codepen.io/web-dot-dev/embed/bGMGqxd?height=500&amp;theme-id=light&amp;default-tab=result&amp;editable=true" style="height: 100%; width: 100%; border: 0;" title="Pen bGMGqxd by web-dot-dev on Codepen"></iframe> View [Step 3: Announcing the number of items on CodePen](https://codepen.io/web-dot-dev/pen/bGMGqxd).

## Add a landmark

With little effort, you've made great improvements for screen reader users, but there's one more thing you can do. The navigation is semantically still just a list of links and it's hard to tell that this specific list is the main navigation of your website. You can turn this ordinary list into a navigational list by wrapping the `<ul>` in a `<nav>` element.

Using the `<nav>` element has several advantages. Notably, a screen reader announces something like "navigation", when the user interacts with it, and it adds a [landmark](https://web.dev/articles/headings-and-landmarks) to the page. Landmarks are special regions on the page, like the `<header>`, `<footer>` or `<main>`, to which a screen reader can jump. Having landmarks on a page can be useful, because it allows screen reader users to access important regions on the page directly without having to interact with the rest of the page. For example, you can jump from landmark to landmark by pressing the **D** key in NVDA. In Voice Over you can use the rotor to list all landmarks on the page by pressing **VO** + **U**.
![A list of four landmarks: banner, navigation, main, content information.](https://web.dev/static/articles/website-navigation/image/a-list-four-landmarks-b-a1d2ac087daef.png) Rotor in VoiceOver listing all the landmarks on a page.

In this list you see 4 landmarks: **banner** which is the `<header>` element, **navigation** is the `<nav>`, **main** the `<main>` element, and **content information** is the `<footer>`. This list shouldn't be too long, you really only want to mark critical parts of your UI as landmarks, like the site search, a local navigation, or a pagination.

If you have a site-wide navigation, a local navigation for the page, and a pagination on a single page, you might also have 3 `<nav>` elements. That's fine, but now there are three navigation landmarks and semantically they all look the same. It's hard to tell them apart, unless you know the structure of the page really well.
![Image showing three landmarks that all say 'navigation'.](https://web.dev/static/articles/website-navigation/image/image-showing-three-landm-f3d5de4194a49.png) The rotor in VoiceOver listing three unlabelled navigation landmarks.

To make them distinguishable, you should label them by either using `aria-labelledby` or `aria-label`.

    <nav aria-label="Main">
        <ul>
          <li>
             <a href="/home">Home</a>
          </li>
          ...
      </ul>
    </nav>
    ...
    <nav aria-label="Select page">
        <ul>
          <li>
             <a href="/page-1">1</a>
          </li>
          ...
        </ul>
    </nav>

If the label you've picked already exists somewhere in the page, you can use `aria-labelledby` instead and reference the existing label using the `id` attribute.

    <nav aria-labelledby="pagination_heading">
      <h2 id="pagination_heading">Select a page</h2>
      <ul>
        <li>
           <a href="/page-1">1</a>
        </li>
        ...
      </ul>
    </nav>

A concise label is sufficient, don't get too wordy. Omit expressions like "navigation" or "menu" because the screen reader already provides users with this information.
![Landmarks](https://web.dev/static/articles/website-navigation/image/landmarks-banner-main-52ee497db7028.png) VoiceOver listing the landmarks "banner", "main navigation", "main", "page navigation", "select page navigation" and "content information". <iframe allow="camera; clipboard-read; clipboard-write; encrypted-media; geolocation; microphone; midi;" loading="lazy" src="https://codepen.io/web-dot-dev/embed/MWGWpqp?height=500&amp;theme-id=light&amp;default-tab=result&amp;editable=true" style="height: 100%; width: 100%; border: 0;" title="Pen MWGWpqp by web-dot-dev on Codepen"></iframe> View [Step 4: Adding a landmark on CodePen](https://codepen.io/web-dot-dev/pen/MWGWpqp).

## Hide the navigation on narrow viewports

Personally, I'm not a big fan of hiding the main navigation on narrow viewports, but if the list of links gets too long, there's no way around it. If that's the case, instead of the list, users see a button labelled "Menu" or a burger icon or a combination. Clicking the button shows and hides the list. If you know basic JavaScript and CSS, that's a doable task, but there are several things in terms of UX and accessibility you have to take care of.

- You have to hide the list in an accessible manner.
- The navigation must be keyboard accessible.
- The navigation must communicate whether or not it's visible.

### Adding a burger button

Since you're following the progressive enhancement principle, you want to make sure that your navigation still works and makes sense even with JavaScript turned off.  

The first thing your navigation needs is a burger button. You create it in HTML in a template element, clone it in JavaScript, and add it to the navigation.
![A page displaying a burger button.](https://web.dev/static/articles/website-navigation/image/a-page-displaying-burger-d1ef89aa72d22.png) Result: Instead of links, the navigation shows a burger button on narrow viewports.

    <nav id="mainnav">
      ...
    </nav>

    <template id="burger-template">
      <button type="button" aria-expanded="false" aria-label="Menu" aria-controls="mainnav">
        <svg width="24" height="24" aria-hidden="true">
          <path d="M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z">
        </svg>
      </button>
    </template>

1. The `aria-expanded` attribute tells the screen reader software whether or not the element the button controls is expanded.
2. `aria-label` gives the button a so-called accessible name, a text alternative for the burger icon.
3. You hide the `<svg>` from assistive technology using `aria-hidden` because it already has a text label provided by `aria-label`.
4. `aria-controls` tells assistive technology, that supports the attribute (for example JAWS), which element the button controls.

    const nav = document.querySelector('#mainnav')
    const list = nav.querySelector('ul');
    const burgerClone = document.querySelector('#burger-template').content.cloneNode(true);
    const button = burgerClone.querySelector('button');

    // Toggle aria-expanded attribute
    button.addEventListener('click', e => {
      // aria-expanded="true" signals that the menu is currently open
      const isOpen = button.getAttribute('aria-expanded') === "true"
      button.setAttribute('aria-expanded', !isOpen);
    });

    // Hide list on keydown Escape
    nav.addEventListener('keyup', e => {
      if (e.code === 'Escape') {
        button.setAttribute('aria-expanded', false);
      }
    });

    // Add the button to the page
    nav.insertBefore(burgerClone, list);

1. It's convenient for users to have the ability to close the navigation whenever they want, for example by pressing the Escape key.
2. It's important to use `insertBefore` instead of `appendChild` because the button should be the first element in your navigation. If a keyboard or screen reader user presses **Tab** after clicking the button, they expect to focus the first item in the list. If the button comes after the list, that would not be the case.

Next, you reset the default styling of the button and make sure that it's only visible on narrow viewports.

    @media (min-width: 48em) {
      nav {
        --nav-button-display: none;
      }
    }

    /* Reset button styling */
    button {
      all: unset;
      display: var(--nav-button-display, flex);
    }

<iframe allow="camera; clipboard-read; clipboard-write; encrypted-media; geolocation; microphone; midi;" loading="lazy" src="https://codepen.io/web-dot-dev/embed/JjvjWaZ?height=500&amp;theme-id=light&amp;default-tab=result&amp;editable=true" style="height: 100%; width: 100%; border: 0;" title="Pen JjvjWaZ by web-dot-dev on Codepen"></iframe> View [Step 5: Adding a burger button on CodePen](https://codepen.io/web-dot-dev/pen/JjvjWaZ).

### Hiding the list

Before you hide the list, position and style the navigation and list so that the layout is optimised for narrow viewports, but still looks good on larger screens.  

First, remove the `<nav>` from the natural flow of the page and place it at the top end corner of the viewport.

    @media (min-width: 48em) {
      nav {
        --nav-button-display: none;
        --nav-position: static;
      }
    }

    nav {
      position: var(--nav-position, fixed);
      inset-block-start: 1rem;
      inset-inline-end: 1rem;
    }

Next, change the layout on narrow viewports by adding a new custom property `(---nav-list-layout)`. The layout is column by default and switches to row on larger screens.

    @media (min-width: 48em) {
      nav {
        --nav-button-display: none;
        --nav-position: static;
      }

      ul {
        --nav-list-layout: row;
      }
    }

    ul {
      display: flex;
      flex-direction: var(--nav-list-layout, column);
      flex-wrap: wrap;
      gap: 1rem;
      list-style: none;
      margin: 0;
      padding: 0;
    }

Your navigation should look something like this on narrow viewports.
![The page showing the navigation list and the burger button.](https://web.dev/static/articles/website-navigation/image/the-page-showing-navigat-7d2e463c7f0d1.png) Both the burger button and the list are placed in the top end corner of the viewport.

The list obviously needs some CSS. We'll move it up to the top end corner, make it fill the whole screen vertically, apply a `background-color` and a `box-shadow`.

    @media (min-width: 48em) {
      nav {
        --nav-button-display: none;
        --nav-position: static;
      }
      
      ul {
        --nav-list-layout: row;
        --nav-list-position: static;
        --nav-list-padding: 0;
        --nav-list-height: auto;
        --nav-list-width: 100%;
        --nav-list-shadow: none;
      }
    }

    ul {
      background: rgb(255, 255, 255);
      box-shadow: var(--nav-list-shadow, -5px 0 11px 0 rgb(0 0 0 / 0.2));
      display: flex;
      flex-direction: var(--nav-list-layout, column);
      flex-wrap: wrap;
      gap: 1rem;
      height: var(--nav-list-height, 100vh);
      list-style: none;
      margin: 0;
      padding: var(--nav-list-padding, 2rem);
      position: var(--nav-list-position, fixed);
      inset-block-start: 0; /* Logical property. Equivalent to top: 0; */
      inset-inline-end: 0; /* Logical property. Equivalent to right: 0; */
      width: var(--nav-list-width, min(22rem, 100vw));
    }

    button {
      all: unset;
      display: var(--nav-button-display, flex);
      position: relative;
      z-index: 1;
    }

The list should look something like this on narrow viewports, more like a sidebar than a simple list.
![The navigation list open.](https://web.dev/static/articles/website-navigation/image/the-navigation-list-open-529d28567d05a.png)

Finally, hide the list, only show it when the user clicks the button once and hide it when they click again. It's important to only hide the list and not the whole navigation because hiding the navigation would also mean hiding an important landmark.

Earlier, you added a click event to the button to toggle the value of the `aria-expanded` attribute. You can use that information as a condition for showing and hiding the list in CSS.

    @media (min-width: 48em) {
      ul {
        --nav-list-visibility: visible;
      }
    }

    ul {
      visibility: var(--nav-list-visibility, visible);
    }

    /* Hide the list on narrow viewports, if it comes after an element with
       aria-expanded set to "false". */
    [aria-expanded="false"] + ul {
      visibility: var(--nav-list-visibility, hidden);
    }

It's important to use a property declaration like `visibility: hidden` or `display: none` instead of `opacity: 0` or `translateX(100%)` to hide the list. These properties make sure that the links are not focusable when the navigation is hidden. Using `opacity` or `translate` will remove content visually so the links would be invisible yet still accessible using the keyboard which would be confusing and frustrating. Using `visibility` or `display` hides it visually and makes it inaccessible, therefore hiding it for all users.
<iframe allow="camera; clipboard-read; clipboard-write; encrypted-media; geolocation; microphone; midi;" loading="lazy" src="https://codepen.io/web-dot-dev/embed/RwywpYO?height=500&amp;theme-id=light&amp;default-tab=result&amp;editable=true" style="height: 100%; width: 100%; border: 0;" title="Pen RwywpYO by web-dot-dev on Codepen"></iframe> View [Step 6: Hiding the list](https://codepen.io/web-dot-dev/pen/RwywpYO).

### Animating the list

If you're wondering why to use `visibility: hidden;` over `display: none;`, it's because you can animate visibility. It only has two states, `hidden` and `visible`, but you can combine it with another property like `transform` or `opacity` to create a slide- or fade-in effect. That wouldn't work with display: none because the display property is not animatable.

The following CSS transitions `opacity` to create a fade-in and fade-out effect.

    ul {
      transition: opacity 0.6s linear, visibility 0.3s linear;
      visibility: var(--nav-list-visibility, visible);
    }

    [aria-expanded="false"] + ul {
      opacity: 0;
      visibility: var(--nav-list-visibility, hidden);
    }

If you want to animate motion instead, you should consider wrapping the `transition` property in a [prefers-reduced-motion](https://developer.mozilla.org/docs/Web/CSS/@media/prefers-reduced-motion) media query because animations can trigger [nausea, dizziness, and headaches](https://alistapart.com/article/accessibility-for-vestibular/) in some users.

    ul {
      visibility: var(--nav-list-visibility, visible);
    }

    @media (prefers-reduced-motion: no-preference) {
      ul {
        transition: transform 0.6s cubic-bezier(.68,-0.55,.27,1.55), visibility 0.3s linear;
      }
    }

    [aria-expanded="false"] + ul {
      transform: var(--nav-list-transform, translateX(100%));
      visibility: var(--nav-list-visibility, hidden);
    }

This makes sure that only people who have no preference for reduced motion will see the animation.
<iframe allow="camera; clipboard-read; clipboard-write; encrypted-media; geolocation; microphone; midi;" loading="lazy" src="https://codepen.io/web-dot-dev/embed/jOxOBeM?height=500&amp;theme-id=light&amp;default-tab=result&amp;editable=true" style="height: 100%; width: 100%; border: 0;" title="Pen jOxOBeM by web-dot-dev on Codepen"></iframe> View [Step 7: Animating the list on CodePen](https://codepen.io/web-dot-dev/pen/jOxOBeM).

## Improve focus styling

Keyboard users rely on the focus styles of elements for orientation and navigation on a page. Default focus styles are better than no focus styles (which happens if you set `outline: none`), but having more clearly visible custom focus styles improves the user experience.

Here's how the default focus styles on the link look in Chrome 103.
![A blue 2px outline around a focused link in Chrome 103.](https://web.dev/static/articles/website-navigation/image/a-blue-2px-outline-around-06dbe05f2f773.png)

You can improve that by providing your own styles in your own colors. By using `:focus-visible` instead of `:focus` you let the browser decide when it's appropriate to show focus styles. `:focus` styles will be visible to everyone, mouse, keyboard and touch users, no matter whether they need them or not. With `:focus-visible` the browser uses internal heuristics to decide whether to show them just to keyboard users or to everyone.

    /* Remove the default :focus outline */
    *:focus {
      outline: none;
    }

    /* Show a custom outline on :focus-visible */
    *:focus-visible {
      outline: 2px solid var(--color-shades-dark);
      outline-offset: 4px;
    }

### Browser support for `:focus-visible`


Browser Support

- ![Chrome: 86.](data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='-10 -10 276 276'%3E%3ClinearGradient id='a' x1='145' x2='34' y1='253' y2='61' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='0' stop-color='%231e8e3e'/%3E%3Cstop offset='1' stop-color='%2334a853'/%3E%3C/linearGradient%3E%3ClinearGradient id='b' x1='111' x2='222' y1='254' y2='62' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='0' stop-color='%23fcc934'/%3E%3Cstop offset='1' stop-color='%23fbbc04'/%3E%3C/linearGradient%3E%3ClinearGradient id='c' x1='17' x2='239' y1='80' y2='80' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='0' stop-color='%23d93025'/%3E%3Cstop offset='1' stop-color='%23ea4335'/%3E%3C/linearGradient%3E%3Ccircle cx='128' cy='128' r='64' fill='%23fff'/%3E%3Cpath fill='url(%23a)' d='M96 183a64 64 0 0 1-23-23L17 64a128 128 0 0 0 111 192l55-96a64 64 0 0 1-87 23Z'/%3E%3Cpath fill='url(%23b)' d='M192 128a64 64 0 0 1-9 32l-55 96A128 128 0 0 0 239 64H128a64 64 0 0 1 64 64Z'/%3E%3Ccircle cx='128' cy='128' r='52' fill='%231a73e8'/%3E%3Cpath fill='url(%23c)' d='M96 73a64 64 0 0 1 32-9h111a128 128 0 0 0-222 0l56 96a64 64 0 0 1 23-87Z'/%3E%3C/svg%3E)
- ![Edge: 86.](data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' width='24' height='24' viewBox='0 0 27600 27600'%3E%3ClinearGradient id='A' gradientUnits='userSpaceOnUse'/%3E%3ClinearGradient id='B' x1='6870' x2='24704' y1='18705' y2='18705' xlink:href='%23A'%3E%3Cstop offset='0' stop-color='%230c59a4'/%3E%3Cstop offset='1' stop-color='%23114a8b'/%3E%3C/linearGradient%3E%3ClinearGradient id='C' x1='16272' x2='5133' y1='10968' y2='23102' xlink:href='%23A'%3E%3Cstop offset='0' stop-color='%231b9de2'/%3E%3Cstop offset='.16' stop-color='%231595df'/%3E%3Cstop offset='.67' stop-color='%230680d7'/%3E%3Cstop offset='1' stop-color='%230078d4'/%3E%3C/linearGradient%3E%3CradialGradient id='D' cx='16720' cy='18747' r='9538' xlink:href='%23A'%3E%3Cstop offset='.72' stop-opacity='0'/%3E%3Cstop offset='.95' stop-opacity='.53'/%3E%3Cstop offset='1'/%3E%3C/radialGradient%3E%3CradialGradient id='E' cx='7130' cy='19866' r='14324' gradientTransform='matrix(.14843 -.98892 .79688 .1196 -8759 25542)' xlink:href='%23A'%3E%3Cstop offset='.76' stop-opacity='0'/%3E%3Cstop offset='.95' stop-opacity='.5'/%3E%3Cstop offset='1'/%3E%3C/radialGradient%3E%3CradialGradient id='F' cx='2523' cy='4680' r='20243' gradientTransform='matrix(-.03715 .99931 -2.12836 -.07913 13579 3530)' xlink:href='%23A'%3E%3Cstop offset='0' stop-color='%2335c1f1'/%3E%3Cstop offset='.11' stop-color='%2334c1ed'/%3E%3Cstop offset='.23' stop-color='%232fc2df'/%3E%3Cstop offset='.31' stop-color='%232bc3d2'/%3E%3Cstop offset='.67' stop-color='%2336c752'/%3E%3C/radialGradient%3E%3CradialGradient id='G' cx='24247' cy='7758' r='9734' gradientTransform='matrix(.28109 .95968 -.78353 .22949 24510 -16292)' xlink:href='%23A'%3E%3Cstop offset='0' stop-color='%2366eb6e'/%3E%3Cstop offset='1' stop-color='%2366eb6e' stop-opacity='0'/%3E%3C/radialGradient%3E%3Cpath id='H' d='M24105 20053a9345 9345 0 01-1053 472 10202 10202 0 01-3590 646c-4732 0-8855-3255-8855-7432 0-1175 680-2193 1643-2729-4280 180-5380 4640-5380 7253 0 7387 6810 8137 8276 8137 791 0 1984-230 2704-456l130-44a12834 12834 0 006660-5282c220-350-168-757-535-565z'/%3E%3Cpath id='I' d='M11571 25141a7913 7913 0 01-2273-2137 8145 8145 0 01-1514-4740 8093 8093 0 013093-6395 8082 8082 0 011373-859c312-148 846-414 1554-404a3236 3236 0 012569 1297 3184 3184 0 01636 1866c0-21 2446-7960-8005-7960-4390 0-8004 4166-8004 7820 0 2319 538 4170 1212 5604a12833 12833 0 007684 6757 12795 12795 0 003908 610c1414 0 2774-233 4045-656a7575 7575 0 01-6278-803z'/%3E%3Cpath id='J' d='M16231 15886c-80 105-330 250-330 566 0 260 170 512 472 723 1438 1003 4149 868 4156 868a5954 5954 0 003027-839 6147 6147 0 001133-850 6180 6180 0 001910-4437c26-2242-796-3732-1133-4392-2120-4141-6694-6525-11668-6525-7011 0-12703 5635-12798 12620 47-3654 3679-6605 7996-6605 350 0 2346 34 4200 1007 1634 858 2490 1894 3086 2921 618 1067 728 2415 728 2952s-271 1333-780 1990z'/%3E%3Cuse fill='url(%23B)' xlink:href='%23H'/%3E%3Cuse fill='url(%23D)' opacity='.35' xlink:href='%23H'/%3E%3Cuse fill='url(%23C)' xlink:href='%23I'/%3E%3Cuse fill='url(%23E)' opacity='.4' xlink:href='%23I'/%3E%3Cuse fill='url(%23F)' xlink:href='%23J'/%3E%3Cuse fill='url(%23G)' xlink:href='%23J'/%3E%3C/svg%3E)
- ![Firefox: 85.](data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 512 512'%3E%3Cdefs%3E%3CradialGradient id='ff-b' cx='428.5' cy='55.1' r='501' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='.1' stop-color='%23ffbd4f'/%3E%3Cstop offset='.2' stop-color='%23ffac31'/%3E%3Cstop offset='.3' stop-color='%23ff9d17'/%3E%3Cstop offset='.3' stop-color='%23ff980e'/%3E%3Cstop offset='.4' stop-color='%23ff563b'/%3E%3Cstop offset='.5' stop-color='%23ff3750'/%3E%3Cstop offset='.7' stop-color='%23f5156c'/%3E%3Cstop offset='.8' stop-color='%23eb0878'/%3E%3Cstop offset='.9' stop-color='%23e50080'/%3E%3C/radialGradient%3E%3CradialGradient id='ff-c' cx='245.4' cy='259.9' r='501' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='.3' stop-color='%23960e18'/%3E%3Cstop offset='.3' stop-color='%23b11927' stop-opacity='.7'/%3E%3Cstop offset='.4' stop-color='%23db293d' stop-opacity='.3'/%3E%3Cstop offset='.5' stop-color='%23f5334b' stop-opacity='.1'/%3E%3Cstop offset='.5' stop-color='%23ff3750' stop-opacity='0'/%3E%3C/radialGradient%3E%3CradialGradient id='ff-d' cx='305.8' cy='-58.6' r='363' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='.1' stop-color='%23fff44f'/%3E%3Cstop offset='.3' stop-color='%23ffdc3e'/%3E%3Cstop offset='.5' stop-color='%23ff9d12'/%3E%3Cstop offset='.5' stop-color='%23ff980e'/%3E%3C/radialGradient%3E%3CradialGradient id='ff-e' cx='190' cy='390.8' r='238.6' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='.3' stop-color='%233a8ee6'/%3E%3Cstop offset='.5' stop-color='%235c79f0'/%3E%3Cstop offset='.7' stop-color='%239059ff'/%3E%3Cstop offset='1' stop-color='%23c139e6'/%3E%3C/radialGradient%3E%3CradialGradient id='ff-f' cx='252.2' cy='201.3' r='126.5' gradientTransform='matrix(1 0 0 1 -48 31)' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='.2' stop-color='%239059ff' stop-opacity='0'/%3E%3Cstop offset='.3' stop-color='%238c4ff3' stop-opacity='.1'/%3E%3Cstop offset='.8' stop-color='%237716a8' stop-opacity='.5'/%3E%3Cstop offset='1' stop-color='%236e008b' stop-opacity='.6'/%3E%3C/radialGradient%3E%3CradialGradient id='ff-g' cx='239.1' cy='34.6' r='171.6' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='0' stop-color='%23ffe226'/%3E%3Cstop offset='.1' stop-color='%23ffdb27'/%3E%3Cstop offset='.3' stop-color='%23ffc82a'/%3E%3Cstop offset='.5' stop-color='%23ffa930'/%3E%3Cstop offset='.7' stop-color='%23ff7e37'/%3E%3Cstop offset='.8' stop-color='%23ff7139'/%3E%3C/radialGradient%3E%3CradialGradient id='ff-h' cx='374' cy='-74.3' r='732.2' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='.1' stop-color='%23fff44f'/%3E%3Cstop offset='.5' stop-color='%23ff980e'/%3E%3Cstop offset='.6' stop-color='%23ff5634'/%3E%3Cstop offset='.7' stop-color='%23ff3647'/%3E%3Cstop offset='.9' stop-color='%23e31587'/%3E%3C/radialGradient%3E%3CradialGradient id='ff-i' cx='304.6' cy='7.1' r='536.4' gradientTransform='rotate(84 303 4)' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='0' stop-color='%23fff44f'/%3E%3Cstop offset='.1' stop-color='%23ffe847'/%3E%3Cstop offset='.2' stop-color='%23ffc830'/%3E%3Cstop offset='.3' stop-color='%23ff980e'/%3E%3Cstop offset='.4' stop-color='%23ff8b16'/%3E%3Cstop offset='.5' stop-color='%23ff672a'/%3E%3Cstop offset='.6' stop-color='%23ff3647'/%3E%3Cstop offset='.7' stop-color='%23e31587'/%3E%3C/radialGradient%3E%3CradialGradient id='ff-j' cx='235' cy='98.1' r='457.1' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='.1' stop-color='%23fff44f'/%3E%3Cstop offset='.5' stop-color='%23ff980e'/%3E%3Cstop offset='.6' stop-color='%23ff5634'/%3E%3Cstop offset='.7' stop-color='%23ff3647'/%3E%3Cstop offset='.9' stop-color='%23e31587'/%3E%3C/radialGradient%3E%3CradialGradient id='ff-k' cx='355.7' cy='124.9' r='500.3' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='.1' stop-color='%23fff44f'/%3E%3Cstop offset='.2' stop-color='%23ffe141'/%3E%3Cstop offset='.5' stop-color='%23ffaf1e'/%3E%3Cstop offset='.6' stop-color='%23ff980e'/%3E%3C/radialGradient%3E%3ClinearGradient id='ff-a' x1='446.9' y1='76.8' x2='47.9' y2='461.8' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='.1' stop-color='%23fff44f'/%3E%3Cstop offset='.1' stop-color='%23ffe847'/%3E%3Cstop offset='.2' stop-color='%23ffc830'/%3E%3Cstop offset='.4' stop-color='%23ff980e'/%3E%3Cstop offset='.4' stop-color='%23ff8b16'/%3E%3Cstop offset='.5' stop-color='%23ff672a'/%3E%3Cstop offset='.5' stop-color='%23ff3647'/%3E%3Cstop offset='.7' stop-color='%23e31587'/%3E%3C/linearGradient%3E%3ClinearGradient id='ff-l' x1='442.1' y1='74.8' x2='102.6' y2='414.3' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='.2' stop-color='%23fff44f' stop-opacity='.8'/%3E%3Cstop offset='.3' stop-color='%23fff44f' stop-opacity='.6'/%3E%3Cstop offset='.5' stop-color='%23fff44f' stop-opacity='.2'/%3E%3Cstop offset='.6' stop-color='%23fff44f' stop-opacity='0'/%3E%3C/linearGradient%3E%3C/defs%3E%3Cpath d='M479 166c-11-25-32-52-49-60a249 249 0 0 1 25 73c-27-68-73-95-111-155a255 255 0 0 1-8-14 44 44 0 0 1-4-9 1 1 0 0 0 0-1 1 1 0 0 0-1 0c-60 35-81 101-83 134a120 120 0 0 0-66 25 71 71 0 0 0-6-5 111 111 0 0 1-1-58c-25 11-44 29-58 44-9-12-9-52-8-60l-8 4a175 175 0 0 0-24 21 210 210 0 0 0-22 26 203 203 0 0 0-32 73l-1 2-2 15a229 229 0 0 0-4 34v1a240 240 0 0 0 477 40l1-9c5-41 0-84-15-121zM202 355l3 1-3-1zm55-145zm198-31z' fill='url(%23ff-a)'/%3E%3Cpath d='M479 166c-11-25-32-52-49-60 14 26 22 53 25 72v1a207 207 0 0 1-206 279c-113-3-212-87-231-197-3-17 0-26 2-40-2 11-3 14-4 34v1a240 240 0 0 0 477 40l1-9c5-41 0-84-15-121z' fill='url(%23ff-b)'/%3E%3Cpath d='M479 166c-11-25-32-52-49-60 14 26 22 53 25 72v1a207 207 0 0 1-206 279c-113-3-212-87-231-197-3-17 0-26 2-40-2 11-3 14-4 34v1a240 240 0 0 0 477 40l1-9c5-41 0-84-15-121z' fill='url(%23ff-c)'/%3E%3Cpath d='m362 195 1 1a130 130 0 0 0-22-29C266 92 322 5 331 0c-60 35-81 101-83 134l9-1c45 0 84 25 105 62z' fill='url(%23ff-d)'/%3E%3Cpath d='M257 210c-1 6-22 26-29 26-68 0-80 41-80 41 3 35 28 64 57 79l4 2 7 3a107 107 0 0 0 31 6c120 6 143-143 57-186 22-4 45 5 58 14-21-37-60-62-105-62l-9 1a120 120 0 0 0-66 25l17 16c16 16 58 33 58 35z' fill='url(%23ff-e)'/%3E%3Cpath d='M257 210c-1 6-22 26-29 26-68 0-80 41-80 41 3 35 28 64 57 79l4 2 7 3a107 107 0 0 0 31 6c120 6 143-143 57-186 22-4 45 5 58 14-21-37-60-62-105-62l-9 1a120 120 0 0 0-66 25l17 16c16 16 58 33 58 35z' fill='url(%23ff-f)'/%3E%3Cpath d='m171 151 5 3a111 111 0 0 1-1-58c-25 11-44 29-58 44 1 0 36 0 54 11z' fill='url(%23ff-g)'/%3E%3Cpath d='M18 261a242 242 0 0 0 231 197 207 207 0 0 0 206-279c8 56-20 110-64 146-86 71-169 43-186 31l-3-1c-50-24-71-70-67-110-42 0-57-35-57-35s38-28 89-4c46 22 90 4 90 4 0-2-42-19-58-35l-17-16a71 71 0 0 0-6-5l-5-3c-18-11-52-11-54-11-9-12-9-51-8-60l-8 4a175 175 0 0 0-24 21 210 210 0 0 0-22 26 203 203 0 0 0-32 73c0 1-9 38-5 57z' fill='url(%23ff-h)'/%3E%3Cpath d='M341 167a130 130 0 0 1 22 29 46 46 0 0 1 4 3c55 50 26 121 24 126 44-36 72-90 64-146-27-68-73-95-111-155a255 255 0 0 1-8-14 44 44 0 0 1-4-9 1 1 0 0 0 0-1 1 1 0 0 0-1 0c-9 5-65 92 10 167z' fill='url(%23ff-i)'/%3E%3Cpath d='M367 199a46 46 0 0 0-4-3l-1-1c-13-9-36-18-58-15 86 44 63 193-57 187a107 107 0 0 1-31-6 131 131 0 0 1-11-5c17 12 99 39 186-31 2-5 31-76-24-126z' fill='url(%23ff-j)'/%3E%3Cpath d='M148 277s12-41 80-41c7 0 28-20 29-26s-44 18-90-4c-51-24-89 4-89 4s15 35 57 35c-4 40 16 85 67 110l3 1c-29-15-54-44-57-79z' fill='url(%23ff-k)'/%3E%3Cpath d='M479 166c-11-25-32-52-49-60a249 249 0 0 1 25 73c-27-68-73-95-111-155a255 255 0 0 1-8-14 44 44 0 0 1-4-9 1 1 0 0 0 0-1 1 1 0 0 0-1 0c-60 35-81 101-83 134l9-1c45 0 84 25 105 62-13-9-36-18-58-14 86 43 63 192-57 186a107 107 0 0 1-31-6 131 131 0 0 1-11-5l-3-1 3 1c-29-15-54-44-57-79 0 0 12-41 80-41 7 0 28-20 29-26 0-2-42-19-58-35l-17-16a71 71 0 0 0-6-5 111 111 0 0 1-1-58c-25 11-44 29-58 44-9-12-9-52-8-60l-8 4a175 175 0 0 0-24 21 210 210 0 0 0-22 26 203 203 0 0 0-32 73l-1 2-2 15a279 279 0 0 0-4 34v1a240 240 0 0 0 477 40l1-9c5-41 0-84-15-121zm-24 13z' fill='url(%23ff-l)'/%3E%3C/svg%3E)
- ![Safari: 15.4.](data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' width='24' height='24' viewBox='195 190 135 135'%3E%3Cdefs%3E%3ClinearGradient id='s-a' x1='132.6' x2='134.4' y1='111.7' y2='-105.3' xlink:href='%23s-b'%3E%3Cstop offset='0' stop-color='%23d2d2d2' /%3E%3Cstop offset='.5' stop-color='%23f2f2f2' /%3E%3Cstop offset='1' stop-color='%23fff' /%3E%3C/linearGradient%3E%3ClinearGradient id='s-b' gradientUnits='userSpaceOnUse' /%3E%3ClinearGradient id='s-c' x1='65.4' x2='67.4' y1='115.7' y2='17.1' xlink:href='%23s-b'%3E%3Cstop offset='0' stop-color='%23005ad5' /%3E%3Cstop offset='.2' stop-color='%230875f0' /%3E%3Cstop offset='.3' stop-color='%23218cee' /%3E%3Cstop offset='.6' stop-color='%2327a5f3' /%3E%3Cstop offset='.8' stop-color='%2325aaf2' /%3E%3Cstop offset='1' stop-color='%2321aaef' /%3E%3C/linearGradient%3E%3ClinearGradient id='s-d' x1='158.7' x2='176.3' y1='96.7' y2='79.5' xlink:href='%23s-b'%3E%3Cstop offset='0' stop-color='%23c72e24' /%3E%3Cstop offset='1' stop-color='%23fd3b2f' /%3E%3C/linearGradient%3E%3CradialGradient id='s-i' cx='-69.9' cy='69.3' r='54' gradientTransform='matrix(.9 -.01 .04 2.72 -9 -120)' xlink:href='%23s-b'%3E%3Cstop offset='0' stop-color='%2324a5f3' stop-opacity='0' /%3E%3Cstop offset='1' stop-color='%231e8ceb' /%3E%3C/radialGradient%3E%3CradialGradient id='s-j' cx='109.3' cy='13.8' r='93.1' gradientTransform='matrix(-.02 1.1 -1.04 -.02 137 -115)' xlink:href='%23s-b'%3E%3Cstop offset='0' stop-opacity='0' /%3E%3Cstop offset='1' stop-color='%235488d6' stop-opacity='0' /%3E%3Cstop offset='1' stop-color='%235d96eb' /%3E%3C/radialGradient%3E%3C/defs%3E%3Crect width='220' height='220' x='22' y='-107' fill='url(%23s-a)' ry='49' transform='matrix(.57 0 0 .57 187 256)' /%3E%3Cg transform='translate(194 190)'%3E%3Ccircle cx='67.8' cy='67.7' fill='url(%23s-c)' paint-order='stroke fill markers' r='54' /%3E%3Ccircle cx='-69.9' cy='69.3' fill='url(%23s-i)' transform='translate(138 -2)' r='54' /%3E%3C/g%3E%3Cellipse cx='120' cy='14.2' fill='url(%23s-j)' rx='93.1' ry='93.7' transform='matrix(.58 0 0 .58 192 250)' /%3E%3Cg transform='matrix(.58 0 0 .57 197 182)'%3E%3Cpath fill='%23cac7c8' d='M46 192h1l72-48-7-9-66 57Z' /%3E%3Cpath fill='%23fbfffc' d='M46 191v1l66-57-7-9-59 65Z' /%3E%3Cpath fill='url(%23s-d)' d='m119 144-7-9 66-57-59 66Z' /%3E%3Cpath fill='%23fb645c' d='m105 126 7 9 66-57-1-1-72 49Z' /%3E%3C/g%3E%3Cpath stroke='%23fff' stroke-linecap='round' stroke-miterlimit='1' stroke-width='1.3' d='m287 278 3-2m-12-17 8-2m-8-3h4m-4-13 8 2m-8 3h4m-1-13 7 3m-4-11 7 4m-2-11 6 6m0-12 6 7m1-11 4 6m4-10 3 7m5-9 2 7m15-7-1 7m10-5-3 7m11-4-4 7m11-2-5 6m16 7-7 4m10 4-7 3m10 6-8 1m8 16-8-2m5 10-7-3m4 11-7-4m2 11-6-5m0 11-5-6m-2 11-4-7m-4 11-3-8m-6 10-1-8m-16 8 2-8m-10 5 3-7m-11 4 4-7m-11 2 5-6m-8 3 3-3m4 8 2-3m5 8 2-4m6 7 1-4m8 5v-4m8 4v-4m9 3-1-4m9 1-2-4m9 0-2-4m9-2-3-3m8-4-3-2m8-5-4-2m7-6-4-1m5-8h-4m4-8h-4m3-9-4 1m1-9-4 2m-1-9-3 2m-2-9-3 3m-4-8-2 3m-5-8-2 4m-6-6-1 3m-8-5v4m-8-4v4m-9-2 1 3m-9 0 2 3m-9 1 2 3m-9 2 3 3m-8 4 3 2m-8 5 4 2m-7 6 4 1m-4 25 4-1m-2 5 7-3m-6 7 4-2m-2 6 7-4m-13-21h8m41-41v-8m0 99v-8m49-42h-8' transform='translate(-65 8)' /%3E%3C/svg%3E)

[Source](https://developer.mozilla.org/docs/Web/CSS/Reference/Selectors/:focus-visible)

<br />

![Clearly visible dark 2px outline with spacing inside.](https://web.dev/static/articles/website-navigation/image/clearly-visible-dark-2px-9fa411b864327.png)

There are different ways of highlighting items when they're focused. Using the `outline` property is recommended because it doesn't break layout, which could happen with `border`, and it works well with [high contrast mode on Windows](https://web.dev/articles/color-and-contrast-accessibility#high_contrast_mode). Properties that don't work well are `background-color` or `box-shadow`, because they might not be displayed at all with custom contrast settings.
![A site with a dark background with the focus highlighted in purple.](https://web.dev/static/articles/website-navigation/image/a-site-a-dark-background-4f9535f7666da.png) <iframe allow="camera; clipboard-read; clipboard-write; encrypted-media; geolocation; microphone; midi;" loading="lazy" src="https://codepen.io/web-dot-dev/embed/ExLxWdQ?height=500&amp;theme-id=light&amp;default-tab=result&amp;editable=true" style="height: 100%; width: 100%; border: 0;" title="Pen ExLxWdQ by web-dot-dev on Codepen"></iframe> View [Step 8: Improve focus styles on CodePen](https://codepen.io/web-dot-dev/pen/ExLxWdQ).

Congratulations! You've built a progressively enhanced, semantically rich, accessible, and mobile friendly main navigation.

There's always something that can be improved, for example:

- You could consider [trapping focus](https://hidde.blog/using-javascript-to-trap-focus-in-an-element/) inside the navigation or making the rest of the page [inert](https://developer.chrome.com/articles/inert) on narrow viewports.
- You can add a [skip link](https://web.dev/articles/headings-and-landmarks#bypass_repetitive_content_with_skip_links) at the top of the page to allow keyboard users to skip the navigation.

If you remember how this article started, with the aim that the solution should "neither be too simple, nor too complicated", that's where we are now. It's possible however to over-engineer a navigation.

## Navigations versus menus

There's a clear difference between navigations and menus. Navigations are collections of links for navigating related documents. Menus are collections of actions to perform in a document. Sometimes these tasks overlap. You might have a navigation that also includes a button that performs an action, like opening a modal window, or you might have a menu where one action is navigating to another page, like a help page. When that's the case, it's important that you don't mash-up ARIA roles, but identify the main purpose of your component and pick the markup and the roles accordingly.

The `<nav>` element has an implicit ARIA role of navigation which is sufficient to communicate that the element is a navigation, but often you see sites also use menu, menubar, and menuitem. Since we sometimes use these terms interchangeably, thinking that combining them to improve the experience for screen reader users might make sense. Before we learn why that's usually not the case, let's have a look at the official definition of these roles.

### The navigation role

> A collection of navigational [elements](https://www.w3.org/TR/wai-aria-1.1/#dfn-element) (usually links) for navigating the document or related documents.
> [navigation (role) WAI-ARIA 1.1](https://www.w3.org/TR/wai-aria/#navigation)

### The menu role

> A menu is often a list of common actions or functions that the user can invoke. The menu [role](https://www.w3.org/TR/wai-aria-1.1/#dfn-role) is appropriate when a list of menu items is presented in a manner similar to a menu on a desktop application.
> [menu (role) WAI-ARIA 1.1](https://www.w3.org/TR/wai-aria/#menu)

### The menubar role

> A presentation of menu that usually remains visible and is usually presented horizontally.
> The menubar role is used to create a menu bar similar to those found in Windows, Mac, and Gnome desktop applications. A menu bar is used to create a consistent set of frequently used commands. Authors *should* ensure that menubar interaction is similar to the typical menu bar interaction in a desktop graphical user interface.
> [menubar (role) WAI-ARIA 1.1](https://www.w3.org/TR/wai-aria/#menubar)

### The menuitem role

> An option in a set of choices contained by a [menu](https://www.w3.org/TR/wai-aria-1.1/#menu) or [menubar](https://www.w3.org/TR/wai-aria-1.1/#menubar).
> [menuitem (role) WAI-ARIA 1.1](https://www.w3.org/TR/wai-aria/#menuitem)

The specification is very clear here, use navigation for navigating the document or related documents and menu only for a list of actions or functions similar to menus in desktop applications. If you're not building the next Google Docs, you probably don't need any of the menu roles for the main navigation.

### When is a menu appropriate?

The primary use of menu items isn't navigation, but to perform actions. Let's say you have a list or table of data and users can perform certain actions on each item in the list. You could add a button to each row and show the actions when the users click the button.

    <ul>
      <li>
        Product 1

        <button aria-expanded="false" aria-controls="options1">Edit</button>

        <div role="menu" id="options1">
          <button role="menuitem">
            Duplicate
          </button>
          <button role="menuitem">
            Delete
          </button>
          <button role="menuitem">
            Disable
          </button>
        </div>
      </li>
      <li>
        Product 2
        ...
      </li>
    </ul>

### Implications of using menu roles

It's really important to use these menu roles wisely because a lot can go wrong.

Menus expect a certain DOM structure. `menuitem` must be a direct child item of `menu`. The following code could break the semantic behaviour:

     <!-- Wrong, don't do this -->
    <ul role="menu">
      <li>
        <a href="#" role="menuitem">Item 1</a>
      </li>
    </ul>

Savvy users expect certain keyboard shortcuts to work with menus and menubars. Based on the [ARIA Authoring Practices Guide (APG)](https://www.w3.org/WAI/ARIA/apg/), this includes:

- **Enter** and **Space** to select menu items.
- Arrow keys in all directions to navigate between items.
- The **Home** and **End** keys to move focus to the first or last items respectively.
- a-z to move focus to the next menu item with a label that starts with the typed character.
- **Esc** to close the menu.

If a screen reader detects a menu, the software might automatically change the browsing mode, enabling usage of the previously mentioned shortcuts. Inexperienced screen reader users might not be able to use the menu because they don't know these shortcuts or how to use them.

That's the same for keyboard users who might expect that they can use **Shift** and **Shift** + **Tab**.

There's a lot to consider when you create menus and menu bars, with whether it's appropriate to use them in the first place leading the way. When you're building a typical website, the nav element with a list and links is all you need. This also includes Single Page Applications (SPA) or web apps. The underlying stack doesn't matter. Unless you're building something that's very close to a desktop application, avoid menu roles.

## Additional resources

- [Fixing Lists](https://www.scottohara.me/blog/2019/01/12/lists-and-safari.html) by Scott O'hara.
- [Don't Use ARIA Menu Roles for Site Nav](https://adrianroselli.com/2017/10/dont-use-aria-menu-roles-for-site-nav.html) by Adrian Roselli.
- [Menus \& Menu Buttons](https://inclusive-components.design/menus-menu-buttons/) by Heydon Pickering.
- [WAI-ARIA menus, and why you should handle them with great care](https://www.marcozehe.de/wai-aria-menus-use-with-care/) by Marco Zehe.
- [Hiding content responsibly](https://kittygiraudel.com/2021/02/17/hiding-content-responsibly/) by Kitty Giraudel.
- [:focus-visible Is Here](https://matthiasott.com/notes/focus-visible-is-here) by Matthias Ott.