Menu


Navigation item


One link in a navigation list, with an optional leading icon, an optional count or dot, and a caret when it opens a group of further links.

Customize the preview


Choices
Show
Flags
View

Options


Navigation item options
ChoiceValuesDefaultWhat it does
sizesmbk-nav-item--size-<value>lgmdsmlg
lg is 40 tall with 16 px text and a 20 px icon, md 32 tall with 14 px text and a 16 px icon, sm 24 tall with 12 px text and a 12 px icon. The app bar, the header and their stacked menus use the large one; the footer uses the middle one. Figma: lg, md, sm (its default is md).
blocksmbk-nav-item--blockOn or offOff
Fills its row with the caret at the far end, for stacked lists. Figma: the expanded mobile menus.

Partssmbk-nav-item__iconsmbk-nav-item__labelsmbk-nav-item__pausesmbk-nav-item__countsmbk-nav-item__dotsmbk-nav-item__caret

Examples


Usage

One link in a navigation list.

With an icon

An icon before the label, with or without the caret.

Sizes

Three heights, each with its own text, icon, padding and gap: 40 with 16 px text, 32 with 14 px, 24 with 12 px. The large one is the default.

States

Rest, the current page, and disabled. Hover lifts the item and turns its words to the link hover color.

Current page by size

Pressed into the bar at every size, its label a weight bolder at the large and middle sizes.

Count and dot

A count after the label, read as "Issues, 18"; a dot saying something changed, read with its own words.

Guidelines


Purpose

One entry in a list of places to go: a header link, a footer column link, a row in the narrow-screen menu. It is quieter than the link component, which is for links inside running text, and it can carry a caret when it opens a group of further links rather than going somewhere itself.

When to use

  • Inside the header, app bar, footer, sidebar and drawer.
  • Any list of destinations laid out as a navigation region.

When not to use

  • A link inside a sentence. Use the link component.
  • An action. Use a button.

Usage rules

Do

  • Keep labels to one or two words.
  • Give every item in one list an icon, or none of them.
  • Mark exactly one item current per list.

Don't

  • Use it outside a navigation region.
  • Give a group opener an address as well; it is one or the other.
Do

Give every item in a list an icon, or none of them, so the labels line up.

Don't

Icons on only some items make the labels start at different places and look unfinished.

Do

Mark exactly one item as the current page, so the reader knows where they are.

Don't

Two current items leave the reader unsure which page they are on.

Anatomy

  • The block: a link when it has an address, a button when it opens a group.
  • The icon part, optional, before the label.
  • The label part.
  • The count part, optional: a small badge with a number after the label, such as open issues.
  • The dot part, optional: a status dot after the label saying something here changed.
  • The pause part, before a count or a dot: a comma only a screen reader hears.
  • The caret part, optional, pointing down and turning up while the group is open.

Behavior

Static as a link; hovering raises it on the raised surface with a highlight edge and a slight lift. The current page sits pressed into the bar at every size, on the canvas surface with an inset shadow and its text in the default color, its label one weight bolder at the large and middle sizes. Every label keeps the room of its bolder self, so an item that becomes the current page keeps its width and the items beside it do not move; markup written by hand gets this by repeating the label's words in the label's data-label attribute. Only the words seen keep that room: words inside the label for a screen reader only, such as a link's "(opens in a new tab)", go in an element marked data-unseen, which the React wrapper leaves out of the copy, so they never widen the item; markup written by hand leaves them out of data-label. As a group opener it is a button whose expanded state the parent component sets; the caret turns with it, with the state motion, and at once under reduced motion. The block flag stretches it across its row with the caret at the far end, which the stacked menus use. Three sizes, each with the text, icon, side padding and gap of its height: the large one, 40 tall with 16 px text and a 20 px icon, is the default and is what the app bar, the header and their stacked menus use; the middle one, 32 tall with 14 px text and a 16 px icon, is what the footer uses; the small one, 24 tall with 12 px text and a 12 px icon, is for dense lists. In a stacked list (the block form) a long label wraps onto more lines rather than being cut short, and the item grows to hold it; in a bar a label keeps one line, and a bar that runs short of room goes to its narrow form.

On a touch screen every item keeps its size and takes an invisible press area out to the comfortable touch target.

Accessibility


What it does for you

  • It is a real link, or a real button when it opens a group, so Enter follows a link and Enter or Space presses a button.
  • A count and a dot are part of the item's name, after a pause: "Issues, 18", "Inbox, changed". The dot never shows the change by color alone: it is read out.
  • A link carries the current-page marker when it is the page the reader is on.
  • A disabled item keeps its text, loses its address, and is announced as unavailable.
  • In a forced-color mode, the current page is drawn in the system's highlight colors.

What you must do

  • Give the dot words of its own with dotLabel when "changed" does not say what it means.
  • Make a group opener carry the expanded state and control the group it opens; the parent component does this.
  • Mark exactly one item current per list, and keep the item inside a navigation region.

How to test

  • Tab to an item: the focus ring shows, in both themes and in a forced-color mode, where the current page is still told from the other items.
  • Press Enter on a link: it is followed. Press Enter, then Space, on a group opener: each opens or closes the group.
  • With a screen reader, an item is read as a link, or a button, by its label, then its count or dot words. The current page is read as current, and a disabled item as unavailable.
  • With text at 200% and the page 320 pixels wide, a label in a stacked menu wraps and the item grows to hold it.
  • On a touch screen, a press just outside the item's edge, within the comfortable touch target, presses it.

Keyboard

Navigation item keys
KeyWhat it does
Tab
Moves focus to the item.
Enter
Follows the link, or presses the group opener.
Space
Presses a group opener. A link does not take it.

Source


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

Stylesheet
nav-item.css
/* smbk-nav-item — Navigation item. Figma "NavItem" 4209:20209 (State × Size: lg, md, sm).
 * One link in a navigation list: header, app bar, footer columns, drawer.
 * An optional icon leads the label and an optional caret trails it. Each size
 * takes the text, icon, side padding and gap of its height. Hover raises the
 * item on the raised surface with a highlight edge and the resting lift. The
 * current page sits pressed into the bar at every size: the canvas surface,
 * the highlight edge and the inset shadow, and its label one weight bolder. */

.smbk-nav-item {
  box-sizing: border-box;
  display: inline-flex;
  align-items: center;
  gap: var(--smbk-space-inline-md);
  height: var(--smbk-size-control-md);
  margin: 0;
  padding: 0 var(--smbk-space-inset-xl);
  border: var(--smbk-border-width-hairline) solid transparent;
  border-radius: var(--smbk-radius-control-sm);
  background: transparent;
  color: var(--smbk-color-text-subtle);
  font-family: var(--smbk-typography-body-md-font-family);
  font-size: var(--smbk-typography-body-md-font-size);
  font-weight: var(--smbk-typography-body-md-font-weight);
  line-height: var(--smbk-typography-body-md-line-height);
  letter-spacing: var(--smbk-typography-body-md-letter-spacing);
  text-align: left;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  touch-action: manipulation;
}

/* The block frames the words it hides for screen readers, so they stay inside it wherever it is placed: without a
   frame they hang from the page itself and stretch it, past the end of an area that scrolls. No weight, so a parent
   that places the block keeps its say. */
:where(.smbk-nav-item) {
  position: relative;
}

@media (hover: hover) {
  .smbk-nav-item:hover {
    border-color: var(--smbk-color-border-rim);
    background: var(--smbk-color-surface-raised);
    color: var(--smbk-color-text-link-hover);
    box-shadow: var(--smbk-elevation-control);
  }
}

.smbk-nav-item[aria-current="page"] {
  border-color: var(--smbk-color-border-rim);
  background: var(--smbk-color-surface-canvas);
  color: var(--smbk-color-text-default);
  font-weight: var(--smbk-typography-body-md-medium-font-weight);
  box-shadow: var(--smbk-elevation-inset-strong);
}

.smbk-nav-item[aria-expanded="true"] {
  color: var(--smbk-color-text-default);
}

/* The navigation menu's item whose panel is open stays raised, as hover raises it (Figma's NavigationMenu, Open). */
.smbk-nav-item.smbk-navigation-menu__trigger[aria-expanded="true"] {
  border-color: var(--smbk-color-border-rim);
  background: var(--smbk-color-surface-raised);
  box-shadow: var(--smbk-elevation-control);
}

.smbk-nav-item:focus-visible {
  outline: var(--smbk-focus-ring-width) solid var(--smbk-color-border-focus);
  outline-offset: var(--smbk-focus-ring-offset);
}

.smbk-nav-item:disabled,
.smbk-nav-item[aria-disabled="true"] {
  color: var(--smbk-color-text-disabled);
  pointer-events: none;
}

.smbk-nav-item__label {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* The label is always as wide as its bolder self: an invisible copy of its words (`data-label`, which the wrapper
   writes) at the current item's weight lies under it with no height, so an item that becomes the current page
   keeps its width and the items beside it stay where they are. The copy is generated content, hidden, with empty
   words for assistive technology, so the label is read once. */
.smbk-nav-item__label[data-label]::after {
  content: attr(data-label);
  content: attr(data-label) / "";
  display: block;
  height: 0;
  overflow: hidden;
  visibility: hidden;
  font-weight: var(--smbk-typography-body-md-medium-font-weight);
  pointer-events: none;
  user-select: none;
}

.smbk-nav-item__icon {
  flex-shrink: 0;
  font-size: var(--smbk-size-icon-md);
}

/* A count (the badge, small) and a dot (the status dot) after the label, each read after a comma
   only a screen reader hears, so the name is "Issues, 18". */
.smbk-nav-item__count,
.smbk-nav-item__dot {
  flex-shrink: 0;
}

/* .smbk-nav-item__pause: hidden from view, kept for screen readers and the keyboard, by the shared rule in visually-hidden/visually-hidden.css. */

.smbk-nav-item__caret {
  flex-shrink: 0;
  font-size: var(--smbk-size-icon-sm);
  transition: transform var(--smbk-motion-state-duration) var(--smbk-motion-state-easing);
}

.smbk-nav-item[aria-expanded="true"] .smbk-nav-item__caret {
  transform: rotate(180deg);
}

/* ---- sizes: lg (the block rules above) 40 tall with 16 text, a 20 icon, 16 at the sides and 8
   between; md 32 tall with 14 text, a 16 icon, 12 and 6; sm 24 tall with 12 text, a 12 icon, 8 and 4.
   The caret is a step smaller than the icon, and 12 at both smaller sizes. The current label takes
   the medium weight of its text style; Body XS has none, so the small current item keeps its weight
   and is marked by its fill, edge and shadow, as Figma draws it. ---- */

.smbk-nav-item--size-md {
  gap: var(--smbk-space-inline-sm);
  height: var(--smbk-size-control-sm);
  padding: 0 var(--smbk-space-inset-lg);
  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);
}

.smbk-nav-item--size-md[aria-current="page"],
.smbk-nav-item--size-md .smbk-nav-item__label[data-label]::after {
  font-weight: var(--smbk-typography-body-sm-medium-font-weight);
}

.smbk-nav-item--size-sm {
  gap: var(--smbk-space-inline-xs);
  height: var(--smbk-size-control-xs);
  padding: 0 var(--smbk-space-inset-md);
  border-radius: var(--smbk-radius-control-xs);
  font-family: var(--smbk-typography-body-xs-font-family);
  font-size: var(--smbk-typography-body-xs-font-size);
  font-weight: var(--smbk-typography-body-xs-font-weight);
  line-height: var(--smbk-typography-body-xs-line-height);
  letter-spacing: var(--smbk-typography-body-xs-letter-spacing);
}

.smbk-nav-item--size-sm[aria-current="page"],
.smbk-nav-item--size-sm .smbk-nav-item__label[data-label]::after {
  font-weight: var(--smbk-typography-body-xs-font-weight);
}

.smbk-nav-item--size-md .smbk-nav-item__icon {
  font-size: var(--smbk-size-icon-sm);
}

.smbk-nav-item--size-sm .smbk-nav-item__icon {
  font-size: var(--smbk-size-icon-xs);
}

.smbk-nav-item:is(.smbk-nav-item--size-md, .smbk-nav-item--size-sm) .smbk-nav-item__caret {
  font-size: var(--smbk-size-icon-xs);
}

/* ---- block: fills its row, label first and caret at the far end ---- */

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

/* In a stacked list a long label wraps onto more lines instead of being cut short with an
   ellipsis, since a navigation label cut short can become ambiguous. The
   item keeps its size's height for one line and grows for more. A nav item in a bar keeps
   one line: a bar that runs short of room goes to its narrow form instead. */
.smbk-nav-item--block {
  height: auto;
  min-height: var(--smbk-size-control-md);
  white-space: normal;
}

.smbk-nav-item--block.smbk-nav-item--size-md {
  min-height: var(--smbk-size-control-sm);
}

.smbk-nav-item--block.smbk-nav-item--size-sm {
  min-height: var(--smbk-size-control-xs);
}

.smbk-nav-item--block .smbk-nav-item__label {
  text-overflow: clip;
  overflow-wrap: break-word;
}

/* ---- motion: the caret turns with the state purpose as what it opens opens. None under reduced motion. ---- */

@media (prefers-reduced-motion: reduce) {
  .smbk-nav-item__caret {
    transition: none;
  }
}

/* ---- touch: the item takes an invisible press area ----
   On a touch screen an invisible area, centred on it, takes the press out to the comfortable target in each direction
   it falls short of it; its look, its focus ring and its place stay as drawn. */

@media (pointer: coarse) {
  .smbk-nav-item {
    position: relative;
  }

  .smbk-nav-item::after {
    content: '';
    position: absolute;
    inset: min(0%, (100% - var(--smbk-size-target-comfortable)) / 2);
  }
}

/* ---- touch: block items one under another stop halfway to each other ----
   Block items stacked a stack's gap apart reach only halfway into it, so each stays one press. Nothing drawn moves. */

@media (pointer: coarse) {
  .smbk-nav-item--block + .smbk-nav-item--block::after {
    inset-block-start: max(min(0%, (100% - var(--smbk-size-target-comfortable)) / 2), var(--smbk-space-stack-sm) / -2);
  }

  .smbk-nav-item--block:has(+ .smbk-nav-item--block)::after {
    inset-block-end: max(min(0%, (100% - var(--smbk-size-target-comfortable)) / 2), var(--smbk-space-stack-sm) / -2);
  }
}

/* ---- 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-nav-item[aria-current="page"] {
    forced-color-adjust: none;
    border-color: Highlight;
    background: Highlight;
    color: HighlightText;
    box-shadow: none;
  }

  .smbk-nav-item[aria-current="page"]:focus-visible {
    outline-color: CanvasText;
  }

  .smbk-nav-item:disabled,
  .smbk-nav-item[aria-disabled="true"] {
    border-color: GrayText;
    color: GrayText;
  }
}
Tokens it reads
Navigation item tokens
Token and CSSValuePreviewFoundation
space.inline.md--smbk-space-inline-mdcomfortable: 8pxcompact: 6pxchanges with densitySpacing
size.control.md--smbk-size-control-mdcomfortable: 40pxcompact: 32pxchanges with densitySize
space.inset.xl--smbk-space-inset-xlcomfortable: 16pxcompact: 12pxchanges with densitySpacing
border-width.hairline--smbk-border-width-hairline1pxBorder width
radius.control.sm--smbk-radius-control-sm4pxRadius
color.text.subtle--smbk-color-text-subtlelight: #4e555edark: #9ba5b1changes with themeColor
typography.body-md--smbk-typography-body-md-font-family--smbk-typography-body-md-font-size--smbk-typography-body-md-font-weight--smbk-typography-body-md-line-height--smbk-typography-body-md-letter-spacing--smbk-typography-body-md-text-transformfontFamily: Geist, Geist Fallback, system-ui, sans-seriffontSize: 16pxfontWeight: 400lineHeight: 1.5letterSpacing: 0px

The quick brown fox

Typography
color.border.rim--smbk-color-border-rimlight: #ffffffe6dark: #ffffff0dchanges with themeColor
color.surface.raised--smbk-color-surface-raisedlight: #f9fafbdark: #232527changes with themeColor
color.text.link-hover--smbk-color-text-link-hoverlight: #111213dark: #f3f5f8changes with themeColor
elevation.control--smbk-elevation-controllight: 0px 0px 1px 0px #373d430d, 0px 1px 2px 0px #373d431a, 0px 2px 4px 0px #373d430ddark: 0px 0px 1px 0px #0000001a, 0px 1px 2px 0px #00000026, 0px 2px 4px 0px #0000001aElevation
color.surface.canvas--smbk-color-surface-canvaslight: #e7ebf0dark: #111213changes with themeColor
color.text.default--smbk-color-text-defaultlight: #232527dark: #e7ebf0changes with themeColor
typography.body-md-medium--smbk-typography-body-md-medium-font-family--smbk-typography-body-md-medium-font-size--smbk-typography-body-md-medium-font-weight--smbk-typography-body-md-medium-line-height--smbk-typography-body-md-medium-letter-spacing--smbk-typography-body-md-medium-text-transformfontFamily: Geist, Geist Fallback, system-ui, sans-seriffontSize: 16pxfontWeight: 500lineHeight: 1.5letterSpacing: 0px

The quick brown fox

Typography
elevation.inset-strong--smbk-elevation-inset-stronglight: inset -1px 2px 2px 0px #373d4333dark: inset -1px 2px 2px 0px #0000004dElevation
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.text.disabled--smbk-color-text-disabledlight: #7c8692dark: #606a75changes with themeColor
size.icon.md--smbk-size-icon-md20pxSize
size.icon.sm--smbk-size-icon-sm16pxSize
motion.state.duration--smbk-motion-state-duration200msMotion
motion.state.easing--smbk-motion-state-easingcubic-bezier(0.65, 0, 0.35, 1)Motion
space.inline.sm--smbk-space-inline-smcomfortable: 6pxcompact: 4pxchanges with densitySpacing
size.control.sm--smbk-size-control-smcomfortable: 32pxcompact: 28pxchanges with densitySize
space.inset.lg--smbk-space-inset-lgcomfortable: 12pxcompact: 8pxchanges with densitySpacing
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
typography.body-sm-medium--smbk-typography-body-sm-medium-font-family--smbk-typography-body-sm-medium-font-size--smbk-typography-body-sm-medium-font-weight--smbk-typography-body-sm-medium-line-height--smbk-typography-body-sm-medium-letter-spacing--smbk-typography-body-sm-medium-text-transformfontFamily: Geist, Geist Fallback, system-ui, sans-seriffontSize: 14pxfontWeight: 500lineHeight: 1.5letterSpacing: 0px

The quick brown fox

Typography
space.inline.xs--smbk-space-inline-xs4pxSpacing
size.control.xs--smbk-size-control-xs24pxSize
space.inset.md--smbk-space-inset-mdcomfortable: 8pxcompact: 6pxchanges with densitySpacing
radius.control.xs--smbk-radius-control-xs2pxRadius
typography.body-xs--smbk-typography-body-xs-font-family--smbk-typography-body-xs-font-size--smbk-typography-body-xs-font-weight--smbk-typography-body-xs-line-height--smbk-typography-body-xs-letter-spacing--smbk-typography-body-xs-text-transformfontFamily: Geist, Geist Fallback, system-ui, sans-seriffontSize: 12pxfontWeight: 400lineHeight: 1.5letterSpacing: 0px

The quick brown fox

Typography
size.icon.xs--smbk-size-icon-xs12pxSize
size.target.comfortable--smbk-size-target-comfortable44pxSize
space.stack.sm--smbk-space-stack-smcomfortable: 8pxcompact: 6pxchanges with densitySpacing
Manifest
nav-item.manifest.json
{
  "name": "nav-item",
  "block": "smbk-nav-item",
  "summary": "One link in a navigation list, with an optional leading icon, an optional count or dot, and a caret when it opens a group of further links.",
  "adapt": "css",
  "axes": {
    "size": {
      "values": [
        "lg",
        "md",
        "sm"
      ],
      "default": "lg",
      "description": "lg is 40 tall with 16 px text and a 20 px icon, md 32 tall with 14 px text and a 16 px icon, sm 24 tall with 12 px text and a 12 px icon. The app bar, the header and their stacked menus use the large one; the footer uses the middle one. Figma: lg, md, sm (its default is md)."
    }
  },
  "flags": {
    "block": {
      "description": "Fills its row with the caret at the far end, for stacked lists. Figma: the expanded mobile menus."
    }
  },
  "parts": [
    "icon",
    "label",
    "pause",
    "count",
    "dot",
    "caret"
  ],
  "figmaNodeId": "4209:20209"
}