Skip to content

Side nav

A vertical list of links in labeled groups, with groups that fold away. On wide screens it’s a column beside the content; on phones the same nav opens as a drawer from the header’s menu button.

CSS
css/components/side-nav.css
JS
js/components/side-nav.js

01 — Examples

Groups and the current page

Headings group the links. A <details> group folds away. The current page has an accent edge and a selected fill, and a group holding it reads as current too.

The drawer

On phones and tablets, the header’s menu button opens the same nav from the left edge. Try it here: the drawer holds this site’s nav.

02 — In a page

Column and drawer

Wrap the page in .with-side-nav for the column, which shows from 64rem. Put the same nav in a .sheet--start dialog for the drawer.

HTML
<div class="with-side-nav">
  <aside class="with-side-nav__aside panel">
    <nav class="side-nav" aria-label="Sections">…</nav>
  </aside>
  <main class="panel-stack">…</main>
</div>

<dialog class="sheet sheet--start" id="site-nav" data-component="sheet" aria-label="Menu">
  <div class="sheet__panel panel">
    <header class="sheet__head">
      <p class="sheet__title label">Menu</p>
      <button class="btn btn--ghost btn--icon" type="button" data-sheet-close aria-label="Close">×</button>
    </header>
    <div class="sheet__body">
      <nav class="side-nav" aria-label="Sections" data-component="side-nav">…</nav>
    </div>
  </div>
</dialog>

03 — Usage

Use it for

  • Sites and apps with more sections than fit in the header.
  • Two levels at most: headings, then a group.

Avoid

  • A handful of pages. The header is enough.
  • Actions such as Log out. Keep the nav to places.

04 — Accessibility

Notes

  • Give each nav a distinct aria-label (“Sections”, “Primary”).
  • Groups are native <details>, so they open with Enter or Space.
  • In the drawer, focus stays inside until it closes; following a link or widening the screen closes it.

05 — Without Jewel’s JS

What the script does

The nav itself is links and native <details>. The script only matters in a .sheet--start drawer: it closes the drawer and keeps the menu button’s aria-expanded in step. The CSS reads only the markup below, so a framework can set it itself and leave out side-nav.js and data-component.

StateSet thisNotes
Current pagearia-current="page" on the .side-nav__linkAccent edge and selected fill. A group holding it reads as current too.
Group openopen on details.side-nav__groupNative. Turns the chevron.
Disabledaria-disabled="true" on a linkDims it and stops clicks.
DrawerThe sheet’s markup, with the nav in .sheet__bodyOpen and close it as on the Sheet page.
Menu buttonaria-expanded="true" while the drawer is open, "false" when it’s closedOn each button that opens that drawer.

Events and keyboard

  • Close the drawer when a link in it is followed, same-page anchors included.
  • Close it when the screen grows past --bp-md (48rem), where the header shows its links again.