NS Design System
v3.0.0 · 257 tokens

58Mobile nav

Below 64rem the link row is replaced by a hamburger and a full-screen sheet. Not a 20rem side drawer: on a phone a drawer leaves a useless strip of page behind it and forces the links to be small. The sheet is a real <dialog>, so the focus trap, Esc and the inert background come from the platform.

Use it for

  • Every navbar — the bar is one component with two link surfaces, not two bars
  • Sections with more than four children, as native <details> so the sheet stays one thumb-reach
  • A pinned foot for the action the sheet exists to make possible

Not for

  • A hamburger on desktop when there is room for the links — hiding navigation that fits is a cost with no benefit
  • A hand-rolled <div class="drawer"> — you would be reimplementing focus trapping, scroll locking, inert and Esc, and shipping three of the four
  • A bottom tab bar: this is a content site, not an app shell

Hamburger

Three hairline rules — the system's own vocabulary, not an icon-font glyph — rotating into the close X. One control for both states, so the user can see where the thing they opened went. Live: click either (the second is held open).

markup
<button type="button" class="ns-burger" aria-expanded="false" aria-label="Menu" style="display:inline-flex" onclick="this.setAttribute('aria-expanded', this.getAttribute('aria-expanded') !== 'true')">
  <span class="ns-burger__bar"></span><span class="ns-burger__bar"></span><span class="ns-burger__bar"></span>
</button>
<button type="button" class="ns-burger" aria-expanded="true" aria-label="Close menu" style="display:inline-flex" onclick="this.setAttribute('aria-expanded', this.getAttribute('aria-expanded') !== 'true')">
  <span class="ns-burger__bar"></span><span class="ns-burger__bar"></span><span class="ns-burger__bar"></span>
</button>

The sheet

Shown here in the page flow (a non-modal <dialog open>) so the anatomy is readable: head with brand and close, mono-indexed rows, <details> groups, and the pinned foot. In product it is opened with showModal() and covers the viewport.

Start learning Sign in

Free · no card12,400 learners

markup
<dialog class="ns-navsheet" open aria-label="Site menu specimen" style="position:static;inline-size:24rem;max-inline-size:100%;block-size:auto;max-block-size:none;border:1px solid var(--color-border);border-radius:var(--radius-card)">
  <div class="ns-navsheet__head">
    <a class="ns-topnav__brand" href="#"><img src="../assets/logo/favicon.svg" alt=""><span class="ns-topnav__brand-name">Namaste Salesforce</span></a>
    <button type="button" class="ns-btn ns-btn--quiet ns-btn--icon ns-navsheet__close" aria-label="Close menu"><i class="ph ph-x" aria-hidden="true"></i></button>
  </div>
  <div class="ns-navsheet__body">
    <a class="ns-navsheet__link" href="#" aria-current="page"><span class="ns-navsheet__index">01</span> Courses <i class="ph ph-caret-right" aria-hidden="true"></i></a>
    <details class="ns-navsheet__group">
      <summary class="ns-navsheet__link"><span class="ns-navsheet__index">02</span> Learn <i class="ph ph-caret-down" aria-hidden="true"></i></summary>
      <div class="ns-navsheet__sub">
        <a class="ns-navsheet__link" href="#">Administrator</a>
        <a class="ns-navsheet__link" href="#">Developer</a>
        <a class="ns-navsheet__link" href="#">Architect</a>
      </div>
    </details>
    <a class="ns-navsheet__link" href="#"><span class="ns-navsheet__index">03</span> Pricing <i class="ph ph-caret-right" aria-hidden="true"></i></a>
    <p class="ns-navsheet__label">Theme</p>
    <div class="ns-themetoggle" role="radiogroup" aria-label="Colour theme">
      <button type="button" class="ns-themetoggle__opt" role="radio" aria-checked="false" aria-label="Light">Light</button>
      <button type="button" class="ns-themetoggle__opt" role="radio" aria-checked="true" aria-label="Match the system setting">Auto</button>
      <button type="button" class="ns-themetoggle__opt" role="radio" aria-checked="false" aria-label="Dark">Dark</button>
    </div>
  </div>
  <div class="ns-navsheet__foot">
    <a class="ns-btn ns-btn--primary" href="#">Start learning</a>
    <a class="ns-btn ns-btn--outline" href="#">Sign in</a>
    <p class="ns-navsheet__meta"><span>Free · no card</span><span>12,400 learners</span></p>
  </div>
</dialog>

At phone width

The real thing, in a 24rem frame — an iframe rather than a narrow div, because media queries answer to the viewport and a narrow div would keep showing the desktop bar. Tap the hamburger.

markup
<iframe src="./demo-navbar.html" title="Navbar at phone width" loading="lazy"></iframe>

Accessibility contract

  • The burger is a <button aria-expanded aria-haspopup="dialog" aria-controls> — the arrow and the announcement can never disagree because they read the same attribute
  • Opened with showModal(), so focus is trapped and the page behind is inert
  • Every way of closing it runs through the dialog's close event, which returns focus to the burger
  • Rows are large: font-size steps up to --size-h4 and the hit area is the full width