Skip to content
Ndop UI

Navigation Menu

A site's navigation: links and buttons that show panels of links.

import { Component } from '@angular/core';
import { NdIcon } from '@ndop/core';
import {
  NavigationMenu,
  NavigationMenuContent,
  NavigationMenuItem,
  NavigationMenuLink,
  NavigationMenuList,
  NavigationMenuTrigger,
  navigationMenuTriggerStyle,
} from '@/ui/navigation-menu/navigation-menu';

/**
 * A site's navigation: two buttons with panels of links, and a top-level
 * link to the current page. Each panel follows its button in the page, so
 * Tab moves into its links.
 */
@Component({
  selector: 'example-navigation-menu-demo',
  imports: [
    NavigationMenu,
    NavigationMenuContent,
    NavigationMenuItem,
    NavigationMenuLink,
    NavigationMenuList,
    NavigationMenuTrigger,
    NdIcon,
  ],
  template: `
    <nav ndNavigationMenu aria-label="Site">
      <ul ndNavigationMenuList>
        <li ndNavigationMenuItem>
          <button ndNavigationMenuTrigger>
            Getting started <svg ndIcon="chevron-down"></svg>
          </button>
          <div ndNavigationMenuContent>
            <ul class="grid w-[300px] gap-1 md:w-[400px] md:grid-cols-2">
              @for (link of guides; track link.href) {
                <li>
                  <a ndNavigationMenuLink [href]="link.href">
                    <span class="font-medium">{{ link.title }}</span>
                    <span class="text-muted-foreground line-clamp-2">
                      {{ link.text }}
                    </span>
                  </a>
                </li>
              }
            </ul>
          </div>
        </li>
        <li ndNavigationMenuItem>
          <button ndNavigationMenuTrigger>
            Components <svg ndIcon="chevron-down"></svg>
          </button>
          <div ndNavigationMenuContent>
            <ul class="grid w-[200px] gap-1">
              @for (link of components; track link.href) {
                <li>
                  <a ndNavigationMenuLink [href]="link.href">{{
                    link.title
                  }}</a>
                </li>
              }
            </ul>
          </div>
        </li>
        <li ndNavigationMenuItem>
          <a ndNavigationMenuLink href="#docs" active [class]="triggerStyle">
            Docs
          </a>
        </li>
      </ul>
    </nav>
  `,
})
export class NavigationMenuDemoExample {
  protected readonly triggerStyle = navigationMenuTriggerStyle;
  protected readonly guides = [
    {
      href: '#introduction',
      title: 'Introduction',
      text: 'Accessible Angular components you copy into your app.',
    },
    {
      href: '#installation',
      title: 'Installation',
      text: 'Set up Ndop in a new or existing Angular app.',
    },
    {
      href: '#theming',
      title: 'Theming',
      text: 'Colors, radius and fonts, as CSS variables.',
    },
    {
      href: '#forms',
      title: 'Forms',
      text: 'Bind Signal Forms, reactive or template-driven forms.',
    },
  ];
  protected readonly components = [
    { href: '#dialog', title: 'Dialog' },
    { href: '#select', title: 'Select' },
    { href: '#tabs', title: 'Tabs' },
  ];
}

A navigation menu is a site's main navigation: links, and buttons that show panels of more links below them. It uses no menu roles, as screen readers expect for links, and each panel stays right after its button in the page, so Tab moves into its links and they're in the server-rendered HTML. A panel opens on click, Enter or Space, or when the pointer rests on its button, and Escape closes it.

Installation

npx @ndop/cli add navigation-menu

Usage

import {
  NavigationMenu,
  NavigationMenuContent,
  NavigationMenuItem,
  NavigationMenuLink,
  NavigationMenuList,
  NavigationMenuTrigger,
  navigationMenuTriggerStyle,
} from '@/ui/navigation-menu/navigation-menu';
<nav ndNavigationMenu aria-label="Site">
  <ul ndNavigationMenuList>
    <li ndNavigationMenuItem>
      <button ndNavigationMenuTrigger>
        Getting started <svg ndIcon="chevron-down"></svg>
      </button>
      <div ndNavigationMenuContent>
        <ul class="grid w-[300px] gap-1 md:w-[400px] md:grid-cols-2">
          @for (link of guides; track link.href) {
            <li>
              <a ndNavigationMenuLink [href]="link.href">
                <span class="font-medium">{{ link.title }}</span>
                <span class="text-muted-foreground line-clamp-2">
                  {{ link.text }}
                </span>
              </a>
            </li>
          }
        </ul>
      </div>
    </li>
    <li ndNavigationMenuItem>
      <button ndNavigationMenuTrigger>
        Components <svg ndIcon="chevron-down"></svg>
      </button>
      <div ndNavigationMenuContent>
        <ul class="grid w-[200px] gap-1">
          @for (link of components; track link.href) {
            <li>
              <a ndNavigationMenuLink [href]="link.href">{{
                link.title
              }}</a>
            </li>
          }
        </ul>
      </div>
    </li>
    <li ndNavigationMenuItem>
      <a ndNavigationMenuLink href="#docs" active [class]="triggerStyle">
        Docs
      </a>
    </li>
  </ul>
</nav>

Accessibility

Implements the Disclosure navigation pattern of the WAI-ARIA Authoring Practices.

Keyboard

Keys the component handles.

Key Action
TabMoves focus through the buttons, top-level links and the open panel's links, in document order.
EnterorSpaceOn a button, shows or hides its panel.
ArrowDownOn a button, opens its panel and focuses its first link.
ArrowRightorArrowLeftOn a top-level button or link, moves focus to the next or previous one, wrapping.
HomeorEndOn a top-level button or link, moves focus to the first or last one.
EscapeCloses the open panel and returns focus to its button.

Focus

  • Opening a panel with Enter, Space or a click keeps focus on the button.
  • Focus leaving the navigation closes the open panel.

Right-to-left

In right-to-left layouts the top-level items run from right to left, so ArrowLeft moves to the next one and ArrowRight to the previous one. Direction comes from the nearest dir attribute.

API reference

NavigationMenu

[ndNavigationMenu]

A site's navigation (@ndop/primitives/navigation-menu): links, and buttons that show panels of links below them. Each panel stays after its button in the page, so its links render on the server. Put it on a nav named with aria-label.

NavigationMenu binding Type Default
[class]
Extra classes, merged last so they win.
string''
[openDelay]
How long the pointer rests on a button before its panel opens, in ms.
number200
[closeDelay]
How long a panel stays after the pointer leaves its item, in ms.
number300
[(value)]
The open item's value, or null when every panel is closed. Two-way bindable with [(value)].
string | nullnull
Data attribute Values
data-slotnavigation-menu
data-viewportfalse
data-state —

NavigationMenuList

[ndNavigationMenuList]

The list of top-level items, on a ul.

NavigationMenuList binding Type Default
[class]
Extra classes, merged last so they win.
string''
Data attribute Values
data-slotnavigation-menu-list

NavigationMenuItem

[ndNavigationMenuItem]

One top-level item, on an li: a link, or a button with its panel.

NavigationMenuItem binding Type Default
[class]
Extra classes, merged last so they win.
string''
[value]
Its value, which the root's value holds while it's open.
stringinjectId('nd-navigation-menu-item')
Data attribute Values
data-slotnavigation-menu-item
data-state —

NavigationMenuTrigger

button[ndNavigationMenuTrigger]

The button that shows its item's panel. Put the chevron icon in it, last; it turns over while the panel is open.

Manages aria-expanded, aria-controls.

NavigationMenuTrigger binding Type Default
[class]
Extra classes, merged last so they win.
string''
[disabled]
Disables it.
booleanfalse
Data attribute Values
data-slotnavigation-menu-trigger
data-state —
data-disabled —

NavigationMenuContent

[ndNavigationMenuContent]

Its item's panel of links, shown below the item from the md breakpoint, and in the flow of the page below it.

Manages id, hidden.

NavigationMenuContent binding Type Default
[class]
Extra classes, merged last so they win.
string''
Data attribute Values
data-slotnavigation-menu-content
data-state —
a[ndNavigationMenuLink]

A link, at the top level or in a panel; active marks the current page. At the top level, give it navigationMenuTriggerStyle to match the buttons.

Manages aria-current.

NavigationMenuLink binding Type Default
[class]
Extra classes, merged last so they win.
string''
[active]
Marks the link to the current page.
booleanfalse
Data attribute Values
data-slotnavigation-menu-link
data-active —

Agent notes

Do

  • Put it on a nav with an aria-label.
  • Mark the link to the current page with active.
  • Give top-level links navigationMenuTriggerStyle so they match the buttons.

Don't

  • Don't put actions or form fields in a panel; use Dropdown Menu or Popover.
  • Don't hide the only link to an important page in a panel.