Menu


Navigation menu


A site header's links, where an item with a caret opens a wide panel of described links and a featured column.

Customize the preview


Choices
View

Options


Navigation menu options
ChoiceValuesDefaultWhat it does
alignsmbk-navigation-menu--align-<value>startendstart
Which edge of the row the open panel lines up with: start for links at the start of a header, end for links at its end, so the panel stays on the page.

Partssmbk-navigation-menu__listsmbk-navigation-menu__itemsmbk-navigation-menu__triggersmbk-navigation-menu__panelsmbk-navigation-menu__columnsmbk-navigation-menu__headingsmbk-navigation-menu__linkssmbk-navigation-menu__linksmbk-navigation-menu__link-iconsmbk-navigation-menu__link-textsmbk-navigation-menu__link-titlesmbk-navigation-menu__link-descriptionsmbk-navigation-menu__featuredsmbk-navigation-menu__featured-textsmbk-navigation-menu__featured-titlesmbk-navigation-menu__featured-descriptionsmbk-navigation-menu__featured-link

Examples


Guidelines


Purpose

A site header's links, where an item with a caret opens a wide panel under the row: columns of links, each with an icon, a title and a line saying what is there, and an optional featured column with a picture and one link. It suits a site with more pages than a row can hold, grouped so a visitor can see what each section offers before going there. For a short list of links under one item, the header's own dropdown is enough.

When to use

  • The main links of a marketing site or a documentation site, with sections that each hold several pages worth describing.
  • A header where a visitor benefits from seeing a section's pages, and what each is for, before choosing one.

When not to use

  • A few links under one item, with no descriptions. Use the header's links, whose dropdown lists them.
  • Actions, such as Rename or Delete. Use the dropdown.
  • An app's own navigation between its views. Use the sidebar or the app bar.

Usage rules

Do

  • Group a section's pages into two or three columns under short headings.
  • Keep each description to one line that says what a reader finds there.
  • Use align="end" when the links sit at the end of the header.

Don't

  • Put actions or form controls in a panel.
  • Make a top item both a link and the opener of a panel: give the section's own page a link in its panel instead.
  • Turn openOnHover on in a row a visitor has to cross to reach the rest of the page.
Do

Give each link a line saying what a visitor finds there.

Don't

A panel of bare titles makes a visitor open every page to learn what is on it; a short list belongs in the header’s dropdown.

Anatomy

  • The block, a navigation landmark, named "Main" unless given another name.
  • The list part, the row of items.
  • The item part, one entry in the row: a navigation item that links, or one with a caret, the trigger part, that opens its panel.
  • The panel part, under the row, lined up with its start or end edge: on the overlay surface with the overlay shadow.
  • The column part, one list of links under an optional heading part that names it.
  • The link part, one link in a panel: the link icon, then the link title over the link description.
  • The featured part, optional, at the panel's end: a picture, the featured title and description, and a link.

Behavior

Pressing an item with a caret opens its panel under the row and raises the item, and the caret turns; pressing it again closes it. One panel is open at a time: opening another closes the first. Escape, a press anywhere outside the item and its panel, or Tab moving focus past the panel's last link close it. The panel drops in and fades as it opens and fades out as it closes; under reduced motion it only fades.

With openOnHover, in a wide space a panel also opens while a mouse rests on its item and closes a moment after the mouse leaves the item and its panel. A press still opens it, and a press on a panel the mouse opened keeps it open, so touch and the keyboard work the same either way. It is off by default: a panel that opens as the pointer passes over the row gets in the way.

The panel lines up with the row's start edge, or with its end edge (align="end") for links at the end of a header, so it stays on the page.

In a narrow space, as on a phone, the row stacks into a list, each item fills it with its caret at the far end, and a panel opens in place under its item, its columns and its featured column one under another. There it stays open until its item is pressed again or Escape is pressed, as a group in a list does.

Accessibility


What it does for you

  • It follows the disclosure pattern for navigation, not a menu: a navigation landmark holding a list, where an item that opens a panel is a button saying whether its panel is open (aria-expanded) and which panel it controls (aria-controls). Screen readers read the links as links, and Tab moves through them in order.
  • The panel follows its button, so Tab goes from the button into the open panel's links, and past the last one to the next item.
  • Escape closes the open panel and puts focus back on its button when focus was inside it.
  • Each column's links are a list named by its heading. The featured link is described by the featured title, so "Read the notes" is heard with what it is about.
  • The current page's link is marked aria-current="page" and sits on the selected surface.

What you must do

  • Give the block a name of its own when a page has more than one navigation landmark, such as "Main" and "Footer".
  • Write each link's title and description so they make sense heard alone, and keep the featured link's words short; the title describes it.
  • Give a featured picture an empty alt when it only decorates, or words when it says something the title does not.
  • In markup written by hand, the buttons need script to open and close their panels and to set aria-expanded; without it, give each item a link to its section's own page instead.

How to test

  • Tab to an item with a caret and press Enter: the panel opens and the button is announced as expanded. Tab moves through its links; Escape closes it and focus is back on the button.
  • Press outside the open panel: it closes.
  • With a screen reader, the block is read as navigation by its name, each item as a link or a button with its state, and each column as a list by its heading.
  • With the page 320 pixels wide, the items stack and a panel opens under its item; nothing scrolls sideways.

Keyboard

Navigation menu keys
KeyWhat it does
Tab
Moves to the next item, or through the open panel's links; past the last link the panel closes.
EnterorSpace
Opens the focused item's panel, or closes it when it is open.
Escape
Closes the open panel and puts focus back on its button.

Source


The files the navigation menu is made of. Its folder on GitHub (opens in a new tab) holds them with its wrapper and its document.

Stylesheet
navigation-menu.css
/* smbk-navigation-menu — Navigation menu. Figma "NavigationMenu" 8217:3602 (State: Closed, Open) and
 * "NavigationLink" 8217:3405 (State: Default, Hover, Focus).
 * A site header's links: a row of navigation items at the medium size; an item with a caret opens a wide panel under
 * the row, holding columns of links (each an icon, a title and a line of description) under small headings, and
 * an optional featured column with a picture, a title and a link. The items are the navigation item component; its
 * stylesheet draws them, the open one raised as hover raises it. The panel drops in and fades out with the floating
 * panels' motion (floating/floating.css names it). In a narrow space (the layout switch) the row stacks into a
 * list and each panel opens in place under its item, its columns one under another. */

.smbk-navigation-menu {
  position: relative;
  color: var(--smbk-color-text-default);
}

.smbk-navigation-menu__list {
  display: flex;
  align-items: center;
  gap: var(--smbk-space-inline-xs);
  margin: 0;
  padding: 0;
  list-style: none;
}

.smbk-navigation-menu__item {
  display: block;
}

/* ---- the panel: under the row, lined up with its start edge (or its end edge, align end). In a wide space narrower
   than its columns, the columns that do not fit wrap under the others rather than leaving the window. ---- */

.smbk-navigation-menu__panel {
  box-sizing: border-box;
  position: absolute;
  top: calc(100% + var(--smbk-space-stack-xs));
  inset-inline-start: 0;
  z-index: var(--smbk-layer-overlay);
  display: flex;
  flex-wrap: wrap;
  gap: var(--smbk-space-inline-lg);
  width: max-content;
  max-width: calc(100vw - 2 * var(--smbk-space-inset-xl));
  padding: var(--smbk-space-inset-lg);
  border: var(--smbk-border-width-hairline) solid var(--smbk-color-border-rim);
  border-radius: var(--smbk-radius-container-md);
  background: var(--smbk-color-surface-overlay);
  box-shadow: var(--smbk-elevation-overlay);
  transform-origin: top;
}

.smbk-navigation-menu--align-end .smbk-navigation-menu__panel {
  inset-inline-start: auto;
  inset-inline-end: 0;
}

.smbk-navigation-menu__column {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

/* A column's heading: Label SM in the subtlest text, a row the medium mark's height, lined up with the links' text. */
.smbk-navigation-menu__heading {
  display: flex;
  align-items: center;
  min-height: var(--smbk-size-mark-lg);
  margin: 0;
  padding: 0 var(--smbk-space-inset-lg);
  color: var(--smbk-color-text-subtlest);
  font-family: var(--smbk-typography-label-sm-font-family);
  font-size: var(--smbk-typography-label-sm-font-size);
  font-weight: var(--smbk-typography-label-sm-font-weight);
  line-height: var(--smbk-typography-label-sm-line-height);
  letter-spacing: var(--smbk-typography-label-sm-letter-spacing);
}

.smbk-navigation-menu__links {
  display: flex;
  flex-direction: column;
  margin: 0;
  padding: 0;
  list-style: none;
}

/* ---- a link in the panel (Figma's NavigationLink): an icon, a title and a line of description ---- */

.smbk-navigation-menu__link {
  box-sizing: border-box;
  display: flex;
  align-items: flex-start;
  gap: var(--smbk-space-inline-md);
  width: var(--smbk-size-width-240);
  max-width: 100%;
  padding: var(--smbk-space-inset-lg);
  border-radius: var(--smbk-radius-control-md);
  color: var(--smbk-color-text-default);
  text-decoration: none;
  -webkit-tap-highlight-color: transparent;
}

@media (hover: hover) {
  .smbk-navigation-menu__link:hover {
    background: var(--smbk-color-surface-hover);
  }
}

.smbk-navigation-menu__link:focus-visible {
  outline: var(--smbk-focus-ring-width) solid var(--smbk-color-border-focus);
  outline-offset: var(--smbk-focus-ring-offset);
  background: var(--smbk-color-surface-hover);
}

.smbk-navigation-menu__link-icon {
  flex-shrink: 0;
  color: var(--smbk-color-icon-subtle);
  font-size: var(--smbk-size-icon-md);
}

.smbk-navigation-menu__link-text,
.smbk-navigation-menu__featured-text {
  display: flex;
  flex-direction: column;
  gap: var(--smbk-space-text-label);
  min-width: 0;
}

.smbk-navigation-menu__link-title,
.smbk-navigation-menu__featured-title {
  margin: 0;
  color: var(--smbk-color-text-default);
  font-family: var(--smbk-typography-label-lg-font-family);
  font-size: var(--smbk-typography-label-lg-font-size);
  font-weight: var(--smbk-typography-label-lg-font-weight);
  line-height: var(--smbk-typography-label-lg-line-height);
  letter-spacing: var(--smbk-typography-label-lg-letter-spacing);
}

/* The current page's link sits on the selected surface. Figma draws no current link; Label LG has no bolder weight. */
.smbk-navigation-menu__link[aria-current="page"] {
  background: var(--smbk-color-surface-selected);
}

.smbk-navigation-menu__link-description,
.smbk-navigation-menu__featured-description {
  margin: 0;
  color: var(--smbk-color-text-subtle);
  font-family: var(--smbk-typography-body-sm-font-family);
  font-size: var(--smbk-typography-body-sm-font-size);
  font-weight: var(--smbk-typography-body-sm-font-weight);
  line-height: var(--smbk-typography-body-sm-line-height);
  letter-spacing: var(--smbk-typography-body-sm-letter-spacing);
}

/* ---- the featured column: a picture, a title, a line and a link, padded as a link is ---- */

.smbk-navigation-menu__featured {
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--smbk-space-stack-sm);
  width: var(--smbk-size-width-240);
  max-width: 100%;
  padding: var(--smbk-space-inset-lg);
}

.smbk-navigation-menu__featured > :first-child {
  align-self: stretch;
}

/* ---- narrow: the row stacks, each item fills it with its caret at the far end, and a panel opens in place under
   its item, its columns and its featured column one under another, set in a step so they read as the item's. ---- */

@container style(--smbk-layout: narrow) {
  .smbk-navigation-menu__list {
    flex-direction: column;
    align-items: stretch;
    gap: 0;
  }

  .smbk-navigation-menu__item > .smbk-nav-item {
    display: flex;
    width: 100%;
    justify-content: space-between;
  }

  .smbk-navigation-menu__panel,
  .smbk-navigation-menu--align-end .smbk-navigation-menu__panel {
    position: static;
    flex-direction: column;
    gap: var(--smbk-space-stack-sm);
    width: auto;
    max-width: none;
    padding: var(--smbk-space-stack-xs) 0 var(--smbk-space-stack-sm) var(--smbk-space-inset-lg);
    border: 0;
    border-radius: 0;
    background: transparent;
    box-shadow: none;
  }

  .smbk-navigation-menu__link,
  .smbk-navigation-menu__featured {
    width: 100%;
  }
}

/* ---- touch: a link in the panel already passes the comfortable target. Stacked in a narrow space, the top items
   touch: where one item meets the next, each keeps its own edge, so each stays one press. Under an open panel an
   item is not touching another, so the nav item's own area reaches out there, as everywhere else. Nothing drawn
   moves. ---- */

@container style(--smbk-layout: narrow) {
  @media (pointer: coarse) {
    .smbk-navigation-menu__item:not(:has(> .smbk-navigation-menu__panel:not([hidden]))) + .smbk-navigation-menu__item > .smbk-nav-item::after {
      inset-block-start: 0;
    }

    .smbk-navigation-menu__item:has(+ .smbk-navigation-menu__item):not(:has(> .smbk-navigation-menu__panel:not([hidden]))) > .smbk-nav-item::after {
      inset-block-end: 0;
    }
  }
}

/* ---- high contrast (forced colors) ---- */
/* In Windows high-contrast mode the browser drops fills and shadows; this puts the meaning back with the
   user's own colors. Nothing here applies outside that mode. */

@media (forced-colors: active) {
  .smbk-navigation-menu__link[aria-current="page"] {
    outline: var(--smbk-border-width-hairline) solid Highlight;
  }
}
Tokens it reads
Navigation menu tokens
Token and CSSValuePreviewFoundation
motion.enter.duration--smbk-motion-enter-duration320msMotion
motion.enter.easing--smbk-motion-enter-easingcubic-bezier(0.16, 1, 0.3, 1)Motion
space.stack.xs--smbk-space-stack-xs4pxSpacing
motion.exit.duration--smbk-motion-exit-duration200msMotion
motion.exit.easing--smbk-motion-exit-easingcubic-bezier(0.7, 0, 0.84, 0)Motion
color.text.default--smbk-color-text-defaultlight: #232527dark: #e7ebf0changes with themeColor
space.inline.xs--smbk-space-inline-xs4pxSpacing
layer.overlay--smbk-layer-overlay1200Stacking order
space.inline.lg--smbk-space-inline-lgcomfortable: 12pxcompact: 8pxchanges with densitySpacing
space.inset.xl--smbk-space-inset-xlcomfortable: 16pxcompact: 12pxchanges with densitySpacing
space.inset.lg--smbk-space-inset-lgcomfortable: 12pxcompact: 8pxchanges with densitySpacing
border-width.hairline--smbk-border-width-hairline1pxBorder width
color.border.rim--smbk-color-border-rimlight: #ffffffe6dark: #ffffff0dchanges with themeColor
radius.container.md--smbk-radius-container-md12pxRadius
color.surface.overlay--smbk-color-surface-overlaylight: #ffffffdark: #2d3237changes with themeColor
elevation.overlay--smbk-elevation-overlaylight: 0px 12px 16px 2px #373d431a, 0px 2px 8px 0px #373d4326dark: 0px 12px 16px 2px #00000026, 0px 2px 8px 0px #00000033Elevation
size.mark.lg--smbk-size-mark-lg24pxSize
color.text.subtlest--smbk-color-text-subtlestlight: #606a75dark: #7c8692changes with themeColor
typography.label-sm--smbk-typography-label-sm-font-family--smbk-typography-label-sm-font-size--smbk-typography-label-sm-font-weight--smbk-typography-label-sm-line-height--smbk-typography-label-sm-letter-spacing--smbk-typography-label-sm-text-transformfontFamily: Geist, Geist Fallback, system-ui, sans-seriffontSize: 12pxfontWeight: 500lineHeight: 1.4letterSpacing: 0px

The quick brown fox

Typography
space.inline.md--smbk-space-inline-mdcomfortable: 8pxcompact: 6pxchanges with densitySpacing
size.width.240--smbk-size-width-240240pxSize
radius.control.md--smbk-radius-control-md6pxRadius
color.surface.hover--smbk-color-surface-hoverlight: #373d4326dark: #606a754dchanges with themeColor
focus.ring.width--smbk-focus-ring-width2pxFocus
color.border.focus--smbk-color-border-focuslight: #cc0d0ddark: #f34336changes with themeColor
focus.ring.offset--smbk-focus-ring-offset2pxFocus
color.icon.subtle--smbk-color-icon-subtlelight: #4e555edark: #c4cdd8changes with themeColor
size.icon.md--smbk-size-icon-md20pxSize
space.text.label--smbk-space-text-label0pxSpacing
typography.label-lg--smbk-typography-label-lg-font-family--smbk-typography-label-lg-font-size--smbk-typography-label-lg-font-weight--smbk-typography-label-lg-line-height--smbk-typography-label-lg-letter-spacing--smbk-typography-label-lg-text-transformfontFamily: Geist, Geist Fallback, system-ui, sans-seriffontSize: 16pxfontWeight: 500lineHeight: 1.4letterSpacing: 0px

The quick brown fox

Typography
color.surface.selected--smbk-color-surface-selectedlight: #373d431adark: #606a7533changes with themeColor
color.text.subtle--smbk-color-text-subtlelight: #4e555edark: #9ba5b1changes with themeColor
typography.body-sm--smbk-typography-body-sm-font-family--smbk-typography-body-sm-font-size--smbk-typography-body-sm-font-weight--smbk-typography-body-sm-line-height--smbk-typography-body-sm-letter-spacing--smbk-typography-body-sm-text-transformfontFamily: Geist, Geist Fallback, system-ui, sans-seriffontSize: 14pxfontWeight: 400lineHeight: 1.5letterSpacing: 0px

The quick brown fox

Typography
space.stack.sm--smbk-space-stack-smcomfortable: 8pxcompact: 6pxchanges with densitySpacing
Manifest
navigation-menu.manifest.json
{
  "name": "navigation-menu",
  "block": "smbk-navigation-menu",
  "summary": "A site header's links, where an item with a caret opens a wide panel of described links and a featured column.",
  "adapt": "css",
  "axes": {
    "align": {
      "values": [
        "start",
        "end"
      ],
      "default": "start",
      "description": "Which edge of the row the open panel lines up with: start for links at the start of a header, end for links at its end, so the panel stays on the page."
    }
  },
  "flags": {},
  "parts": [
    "list",
    "item",
    "trigger",
    "panel",
    "column",
    "heading",
    "links",
    "link",
    "link-icon",
    "link-text",
    "link-title",
    "link-description",
    "featured",
    "featured-text",
    "featured-title",
    "featured-description",
    "featured-link"
  ],
  "figmaNodeId": "8217:3602"
}