Sidebar

nav.sidebar

Sidebar — the vertical navigation panel of an application or a documentation site: a scrollable column of grouped links, with a heading over each group.

Examples

A documentation sidebar

View “A documentation sidebar” as
<nav class="sidebar nav nav-vertical" aria-label="Documentation"
     style="block-size: 22rem; inline-size: 16rem">
  <div class="sidebar-header">
    <p class="sidebar-title">mostlycss</p>
  </div>

  <div class="sidebar-group">
    <h2 class="sidebar-heading" id="sidebar-start">Getting started</h2>
    <ul class="nav-list" aria-labelledby="sidebar-start">
      <li><a class="nav-link" href="#install">Installation</a></li>
      <li><a class="nav-link" href="#tokens" aria-current="page">Tokens</a></li>
      <li><a class="nav-link" href="#skins">Skins and modes</a></li>
    </ul>
  </div>

  <div class="sidebar-group">
    <h2 class="sidebar-heading" id="sidebar-components">Components</h2>
    <ul class="nav-list" aria-labelledby="sidebar-components">
      <li><a class="nav-link" href="#button">Button</a></li>
      <li><a class="nav-link" href="#nav">Nav</a></li>
    </ul>
  </div>
</nav>

The links are the nav component — this file is only the panel around them.

.nav on the root is what puts the --nav-* tokens in scope, so retuning link padding here is one declaration rather than a second set of tokens with the same job. The size is inline here because this is a demo: .sidebar sets no width and no height, on purpose, and .sidebar-sticky measures itself against the viewport — which is right in a page shell and turns a boxed example into a full-height column. The next example is the real placement.

Where it sits is your job

View “Where it sits is your job” as

Installing

A single link tag is the whole integration.

<div class="docs-layout">
  <nav class="sidebar nav nav-vertical" aria-label="Documentation">
    <div class="sidebar-group">
      <h2 class="sidebar-heading" id="g1">Guides</h2>
      <ul class="nav-list" aria-labelledby="g1">
        <li><a class="nav-link" href="#a" aria-current="page">Installing</a></li>
        <li><a class="nav-link" href="#b">Theming</a></li>
      </ul>
    </div>
  </nav>
  <article>
    <h2>Installing</h2>
    <p>A single link tag is the whole integration.</p>
  </article>
</div>

The sidebar sets no width at all. One line in your own sheet does it: .docs-layout { display: grid; grid-template-columns: 16rem minmax(0, 1fr); }. Layout primitives are out of scope for this library.

Tokens 21

Level 2, declared on .sidebar itself. Set any of them on that selector to restyle this component without touching the skin.

Level 2 tokens declared by sidebar
TokenDefault
--sidebar-edge-padding-blockvar(--space-3)
--sidebar-gapvar(--space-6)
--sidebar-offset0px
--sidebar-padding-blockvar(--space-4)
--sidebar-padding-inlinevar(--space-3)
--sidebar-scroll-paddingvar(--space-4)
--sidebar-title-colorvar(--color-on-surface)
--sidebar-title-font-familyvar(--font-primary)
--sidebar-title-font-sizevar(--font-size-base)
--sidebar-title-font-weightvar(--font-weight-semibold)
--sidebar-heading-colorvar(--color-on-surface-muted)
--sidebar-heading-font-familyvar(--font-primary)
--sidebar-heading-font-sizevar(--font-size-xs)
--sidebar-heading-font-weightvar(--font-weight-semibold)
--sidebar-heading-letter-spacingnormal
--sidebar-heading-margin-block-endvar(--space-1)
--sidebar-heading-padding-inlinevar(--space-3)
--sidebar-bgvar(--color-surface)
--sidebar-border-colorvar(--color-border-subtle)
--sidebar-border-widthvar(--border-width)
--sidebar-colorvar(--color-on-surface)

Variants and states

Variants

  • .sidebar-flush
  • .sidebar-lg
  • .sidebar-sm
  • .sidebar-sticky
  • .sidebar-sunken

Inside it

  • .sidebar-footer
  • .sidebar-header
  • .sidebar-end
  • .sidebar-group
  • .sidebar-heading
  • .sidebar-title

State it reads

None.

Before you ship it

What you have to do 5 requirements

  1. Name the landmark. aria-label on the <nav>. A docs page has a primary nav too, and two unnamed navigation landmarks are indistinguishable.
  2. Give the sidebar a height, or it will not scroll. Independent scrolling needs something to overflow. .sidebar-sticky is the usual answer; without it, constrain block-size yourself. The overflow-y: auto here is inert on an unconstrained element, which is the correct fallback: the panel grows and the page scrolls.
  3. Set --sidebar-offset under a sticky navbar. .sidebar-sticky pins to the top of the viewport; a fixed bar above it needs its height subtracted, or the first group hides behind it.
  4. Give each heading an id and point its list's aria-labelledby at it. Unique per page — repeated ones silently label the wrong group.
  5. Mark the current page with aria-current="page", once, on the anchor.

src/css/components/sidebar.css · npx mostlycss add sidebar