A site header's links, where an item with a caret opens a wide panel of described links and a featured column.
Options
| Choice | Values | Default | What it does |
|---|---|---|---|
alignsmbk-navigation-menu--align-<value> | startend | start | 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. |
Examples
Usage
A site header’s links with the Product panel open: two columns of described links and a featured column.
Closed
Closed: the items with a caret open a panel, the others are plain links.
At the end of a header
Links at the end of a header line their panel up with the row’s end edge.
Mobile
In a narrow space the items stack and a panel opens in place under its item.
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
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
openOnHoveron in a row a visitor has to cross to reach the rest of the page.
Give each link a line saying what a visitor finds there.
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
altwhen 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
| Key | What 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
/* 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
| Token and CSS | Value | Preview | Foundation |
|---|---|---|---|
| motion.enter.duration--smbk-motion-enter-duration | 320ms | Motion | |
| motion.enter.easing--smbk-motion-enter-easing | cubic-bezier(0.16, 1, 0.3, 1) | Motion | |
| space.stack.xs--smbk-space-stack-xs | 4px | Spacing | |
| motion.exit.duration--smbk-motion-exit-duration | 200ms | Motion | |
| motion.exit.easing--smbk-motion-exit-easing | cubic-bezier(0.7, 0, 0.84, 0) | Motion | |
| color.text.default--smbk-color-text-default | light: #232527dark: #e7ebf0changes with theme | Color | |
| space.inline.xs--smbk-space-inline-xs | 4px | Spacing | |
| layer.overlay--smbk-layer-overlay | 1200 | Stacking order | |
| space.inline.lg--smbk-space-inline-lg | comfortable: 12pxcompact: 8pxchanges with density | Spacing | |
| space.inset.xl--smbk-space-inset-xl | comfortable: 16pxcompact: 12pxchanges with density | Spacing | |
| space.inset.lg--smbk-space-inset-lg | comfortable: 12pxcompact: 8pxchanges with density | Spacing | |
| border-width.hairline--smbk-border-width-hairline | 1px | Border width | |
| color.border.rim--smbk-color-border-rim | light: #ffffffe6dark: #ffffff0dchanges with theme | Color | |
| radius.container.md--smbk-radius-container-md | 12px | Radius | |
| color.surface.overlay--smbk-color-surface-overlay | light: #ffffffdark: #2d3237changes with theme | Color | |
| elevation.overlay--smbk-elevation-overlay | light: 0px 12px 16px 2px #373d431a, 0px 2px 8px 0px #373d4326dark: 0px 12px 16px 2px #00000026, 0px 2px 8px 0px #00000033 | Elevation | |
| size.mark.lg--smbk-size-mark-lg | 24px | Size | |
| color.text.subtlest--smbk-color-text-subtlest | light: #606a75dark: #7c8692changes with theme | Color | |
| 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-transform | fontFamily: Geist, Geist Fallback, system-ui, sans-seriffontSize: 12pxfontWeight: 500lineHeight: 1.4letterSpacing: 0px | The quick brown fox | Typography |
| space.inline.md--smbk-space-inline-md | comfortable: 8pxcompact: 6pxchanges with density | Spacing | |
| size.width.240--smbk-size-width-240 | 240px | Size | |
| radius.control.md--smbk-radius-control-md | 6px | Radius | |
| color.surface.hover--smbk-color-surface-hover | light: #373d4326dark: #606a754dchanges with theme | Color | |
| focus.ring.width--smbk-focus-ring-width | 2px | Focus | |
| color.border.focus--smbk-color-border-focus | light: #cc0d0ddark: #f34336changes with theme | Color | |
| focus.ring.offset--smbk-focus-ring-offset | 2px | Focus | |
| color.icon.subtle--smbk-color-icon-subtle | light: #4e555edark: #c4cdd8changes with theme | Color | |
| size.icon.md--smbk-size-icon-md | 20px | Size | |
| space.text.label--smbk-space-text-label | 0px | Spacing | |
| 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-transform | fontFamily: Geist, Geist Fallback, system-ui, sans-seriffontSize: 16pxfontWeight: 500lineHeight: 1.4letterSpacing: 0px | The quick brown fox | Typography |
| color.surface.selected--smbk-color-surface-selected | light: #373d431adark: #606a7533changes with theme | Color | |
| color.text.subtle--smbk-color-text-subtle | light: #4e555edark: #9ba5b1changes with theme | Color | |
| 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-transform | fontFamily: Geist, Geist Fallback, system-ui, sans-seriffontSize: 14pxfontWeight: 400lineHeight: 1.5letterSpacing: 0px | The quick brown fox | Typography |
| space.stack.sm--smbk-space-stack-sm | comfortable: 8pxcompact: 6pxchanges with density | Spacing |
Manifest
{
"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"
}