Skip to content
RU
Scale
Density
Theme
Accent

Navigation

Navigation

The list of an application's sections in the side column. The current item is marked by more than colour: a load-bearing bar stands at its edge.

The component
Markup
<nav class="inst-nav" aria-label="Sections">
  <div class="inst-nav-group">
    <span class="inst-nav-label">Work</span>
    <a class="inst-nav-item" href="#" aria-current="page">
      <svg class="inst-icon" aria-hidden="true"><use href="#i-grid"/></svg>Runs
      <span class="inst-badge inst-nav-count">7</span></a>
    <a class="inst-nav-item" href="#">
      <svg class="inst-icon" aria-hidden="true"><use href="#i-list"/></svg>Queue</a>
    <a class="inst-nav-item" href="#">
      <svg class="inst-icon" aria-hidden="true"><use href="#i-chart"/></svg>Reports</a>
  </div>
  <div class="inst-nav-group">
    <span class="inst-nav-label">Setup</span>
    <a class="inst-nav-item" href="#">
      <svg class="inst-icon" aria-hidden="true"><use href="#i-users"/></svg>Access</a>
    <a class="inst-nav-item" href="#">
      <svg class="inst-icon" aria-hidden="true"><use href="#i-settings"/></svg>Parameters</a>
  </div>
</nav>

Contract

What Required Why
A <nav> + an aria-label yes There is more than one navigation on a screen (the side one, breadcrumbs, tabs, pages) — with no name they are indistinguishable in the list of landmarks
An <a href> on an item yes Navigation is an address. A button breaks the middle click, "open in a new tab" and copying the link
An aria-current="page" yes The only source of the current state. There is no class for it
An aria-hidden="true" on the icon yes The name of the item is given by the label
The current item by a bar at the edge rather than by colour alone yes A carrier that does not depend on colour. aria-current tells a screen reader the same

Accessibility

The keyboard All of it from native links: Tab traverses, Enter goes. instrument.js intercepts nothing, and navigation needs no roving tabindex
The landmark A <nav aria-label> is a destination for "go to a landmark" in a screen reader
Colour is not the only carrier The current item carries a bar at its edge and an aria-current rather than a shade of background alone
The tap target The height of an item is --control-h-md, 32px. At density compact it shrinks: apply compact to tabular views rather than to the main navigation
Contrast The label --text-secondary holds 4.5:1 on the muted surface of the column in five themes
Print The side column is not printed: it is the chrome of the application rather than data

Anatomy

inst-nav-group gathers the items into a meaningful block and inst-nav-label labels it. The gap between groups is set between neighbours (.inst-nav-group + .inst-nav-group) rather than around each — otherwise the first group would step away from the top edge of the column twice as far as from the sides.

The label is an ordinary sentence at size --text-2xs in the colour --text-muted. There are no capitals here: a label has no right to sound louder than the items it labels.

States

Example
Markup
<nav class="inst-nav" aria-label="An example of the current item">
  <div class="inst-nav-group">
    <a class="inst-nav-item" href="#" aria-current="page">Runs</a>
    <a class="inst-nav-item" href="#">Queue</a>
  </div>
</nav>

The mark is set by the attribute aria-current="page" rather than by a class. The styling is caught by the selector [aria-current]:not([aria-current="false"]), so an aria-current="false" on the other items is allowed and highlights nothing.

The mark of the current one What it gives
A bar at the inner edge A box-shadow: inset of width --size-marker. A carrier that does not depend on colour
The surface --surface-selected
The colour of the label --accent-text
The weight --weight-medium. There is no weight 700 in the library

Behavior

In a narrow shell

Inside a .inst-shell-aside below 60rem the column unfolds into a horizontal strip: .inst-nav and .inst-nav-group line up in a row, .inst-nav-label is hidden and the labels of the items stop wrapping. That is the one place where navigation changes shape, and it is done by the shell rather than by the navigation itself.

Composition

A counter in an item

Example
Markup
<nav class="inst-nav" aria-label="An example with a counter">
  <div class="inst-nav-group">
    <a class="inst-nav-item" href="#">Queue
      <span class="inst-badge inst-nav-count">7</span></a>
    <a class="inst-nav-item" href="#">History</a>
  </div>
</nav>

inst-nav-count is a modifier of position: a margin-inline-start: auto pushes the badge to the far edge of the item. The badge itself stays a badge and reads the same tokens.

API

Classes
NameWhat it does
class
inst-navThe container of the list. A column with a gap of --space-1
inst-nav-groupA meaningful block of items
inst-nav-labelThe label of a group
inst-nav-itemAn item. A link rather than a button
inst-nav-countPushes a counter to the far edge of an item
Tokens
NameValueWhere from
token
--control-h-md32pxbase +14: density · scale
--radius-sm6pxbase +4: scale
--text-sm0.875rembase +4: scale
--text-2xs0.6875rembase +4: scale
--weight-medium600one across every cell
--space-12pxone across every cell
--space-36pxone across every cell
--gap-inlinevar(--space-4)base +14: density · scale
--pad-panelvar(--space-5)base +14: density · scale
--size-marker2pxbase +4: scale
--surface-hoverlight-dark(oklch(0 0 0 / 0.035), oklch(1 0 0 / 0.045))one across every cell
--surface-selectedcolor-mix(in oklab, var(--a-4) 14%, transparent)one across every cell
--accent-solidvar(--a-4)one across every cell
--accent-textlight-dark(var(--a-5), var(--a-3))base +1: theme
--text-secondarylight-dark(var(--n-9), var(--n-5))one across every cell
--text-mutedlight-dark(var(--n-8), var(--n-6))one across every cell
--dur-180msone across every cell
--ease-outcubic-bezier(0.22, 0.61, 0.36, 1)one across every cell

Why the kit works this way — the constitution · Open source under MIT, github.com/keshon/instrument