Side Menu Structure
The side menu sits inside the content layout wrapper alongside the main content area. It uses the drawer component internally for scrollable, collapsible list navigation.
aside.nds-sidemenu
├── button.nds-sidemenu-toggle.nds-btn.nds-peek
│ ├── i.hgi.hgi-stroke.hgi-menu-02.nds-icon
│ └── span.nds-label.nds-truncate (toggle label, hidden on desktop)
│
└── nav.nds-drawer.nds-divided.nds-full-height
└── div.nds-scroll-more.nds-divided
├── ul.nds-drawer-list.nds-scroll-more-content
│ ├── li (flat link)
│ │ └── a.nds-btn.nds-subtle.nds-indicator
│ │ └── span.nds-label
│ └── li (accordion section)
│ ├── button.nds-btn.nds-subtle.nds-menu-btn.nds-indicator
│ │ ├── span.nds-label
│ │ └── span.nds-tag (optional count)
│ └── ul (submenu)
│ ├── li (flat link)
│ │ └── a.nds-btn.nds-subtle.nds-indicator
│ │ └── span.nds-label
│ └── li.nds-drawer-group (optional 3rd level)
│ ├── button.nds-btn.nds-subtle.nds-indicator
│ │ ├── span.nds-label
│ │ └── span.nds-tag (optional count)
│ └── ul (group submenu)
│ └── li
│ └── a.nds-btn.nds-subtle.nds-indicator
│ └── span.nds-label
└── button.nds-show-more.nds-btn.nds-subtle
└── i.hgi.hgi-stroke.hgi-arrow-down-01
Default Side Menu
The standard sidebar layout with flat links and collapsible accordion groups. On desktop it stays fixed beside the content. On tablet and mobile it slides in from the edge with a toggle button.
<aside class="nds-sidemenu" aria-label="Sidebar">
<button class="nds-sidemenu-toggle nds-btn nds-peek" aria-label="Sidebar Menu" hidden>
<i class="nds-icon nds-hgi-menu-02" aria-hidden="true"></i>
<span class="nds-label nds-truncate" hidden>Side menu</span>
</button>
<nav class="nds-drawer nds-divided nds-full-height" hidden>
<div class="nds-scroll-more nds-divided">
<ul class="nds-drawer-list nds-scroll-more-content">
<!-- Flat link -->
<li data-state="active">
<a class="nds-btn nds-subtle nds-indicator" href="/">
<span class="nds-label">Home</span>
</a>
</li>
<!-- Accordion group -->
<li>
<button class="nds-btn nds-subtle nds-indicator" aria-expanded="false">
<span class="nds-label">Components</span>
</button>
<ul>
<li>
<a class="nds-btn nds-subtle nds-indicator" href="/components/accordion.html">
<span class="nds-label">Accordion</span>
</a>
</li>
<li>
<a class="nds-btn nds-subtle nds-indicator" href="/components/alert.html">
<span class="nds-label">Alert</span>
</a>
</li>
<li>
<a class="nds-btn nds-subtle nds-indicator" href="/components/button.html">
<span class="nds-label">Buttons</span>
</a>
</li>
</ul>
</li>
<!-- Another accordion group -->
<li>
<button class="nds-btn nds-subtle nds-indicator" aria-expanded="false">
<span class="nds-label">Layout</span>
</button>
<ul>
<li>
<a class="nds-btn nds-subtle nds-indicator" href="/layout/grid.html">
<span class="nds-label">Grid</span>
</a>
</li>
<li>
<a class="nds-btn nds-subtle nds-indicator" href="/layout/section.html">
<span class="nds-label">Section</span>
</a>
</li>
</ul>
</li>
</ul>
<button class="nds-show-more nds-btn nds-subtle">
<i class="nds-icon nds-hgi-arrow-down-01" aria-hidden="true"></i>
</button>
</div>
</nav>
</aside>
Grouped Navigation
Add a third level by placing li.nds-drawer-group items inside a section's submenu. Each group gets its own collapsible button. Use a count tag to show the number of items at a glance.
<ul class="nds-drawer-list">
<!-- Level 1: Section -->
<li>
<button class="nds-btn nds-subtle nds-indicator" aria-expanded="false">
<span class="nds-label">Components</span>
<span class="nds-tag nds-gray nds-xs nds-rounded"><span class="nds-label">10</span></span>
</button>
<ul>
<!-- Level 2: Group -->
<li class="nds-drawer-group">
<button class="nds-btn nds-subtle nds-indicator" aria-expanded="false">
<span class="nds-label">Forms</span>
<span class="nds-tag nds-gray nds-xs nds-rounded"><span class="nds-label">4</span></span>
</button>
<ul>
<!-- Level 3: Pages -->
<li>
<a class="nds-btn nds-subtle nds-indicator" href="/components/autocomplete.html">
<span class="nds-label">Autocomplete</span>
</a>
</li>
<li>
<a class="nds-btn nds-subtle nds-indicator" href="/components/checkbox.html">
<span class="nds-label">Checkbox</span>
</a>
</li>
<li data-state="active">
<a class="nds-btn nds-subtle nds-indicator" href="/components/switch.html">
<span class="nds-label">Switch</span>
</a>
</li>
<li>
<a class="nds-btn nds-subtle nds-indicator" href="/components/upload.html">
<span class="nds-label">Upload</span>
</a>
</li>
</ul>
</li>
<!-- More groups... -->
</ul>
</li>
</ul>
Responsive Modes
On screens below 960px the side menu switches from a persistent sidebar to one of two mobile-friendly patterns, controlled by the layout wrapper class.
Current mode: Slider (default). Resize the viewport below 960px to see the side menu behavior.
<!-- Add nds-top to the sidemenu to switch from slider to top dropdown mode -->
<aside class="nds-sidemenu nds-top" aria-label="Sidebar">...</aside>
Mode Comparison
| Mode | Class / Front Matter | Mobile Behavior | Best For |
|---|---|---|---|
| Slider | (none, default) | Fixed panel slides in from the edge with a floating toggle button. Body scroll is not locked. | Long navigation lists where users may need to scroll the menu independently |
| Top Submenu | nds-top |
Full-width bar with the active page label. Tapping it drops the drawer down from below the header. Body scroll is locked. | Short navigation lists or content-heavy pages where edge slide-in would feel intrusive |
Active Page Tracking
Mark the current page by adding data-state="active" to its <li> element. The JS automatically expands all parent accordion groups so the active item is visible on load.
<ul class="nds-drawer-list">
<li>
<button class="nds-btn nds-subtle nds-indicator" aria-expanded="false">
<span class="nds-label">Components</span>
</button>
<ul>
<li>
<a class="nds-btn nds-subtle nds-indicator" href="/components/accordion.html">
<span class="nds-label">Accordion</span>
</a>
</li>
<!-- Active item: parent group auto-expands on init -->
<li data-state="active">
<a class="nds-btn nds-subtle nds-indicator" href="/components/alert.html">
<span class="nds-label">Alert</span>
</a>
</li>
</ul>
</li>
</ul>
Built-in Features
Activates when .nds-sidemenu is on the page. Toggle button, close-on-click-outside, and Escape key handling all attach automatically. Accordion behavior is provided by the Drawer component.
Set data-state="active" on a menu item and all parent accordion groups expand on load so the current page is always visible.
Choose between a slide-in sidebar panel or a top dropdown bar for mobile. Switch modes by adding nds-top to the sidemenu or setting sidemenu_mode: top in front matter.
Nested groups expand and collapse with smooth height transitions. Opening one group automatically closes its siblings.
The floating toggle button reveals itself briefly on page load and reappears as the cursor approaches, giving users a visual hint without obstructing content.
Call NDS.Sidemenu.init() to re-initialize after dynamic content changes or SPA route transitions.
Usage Guidelines
Best Practices
- Use the side menu for sites with hierarchical page structures where users need to jump between sections: documentation, admin consoles, multi-step workflows
- Use the side menu alongside the Header for primary navigation. The header handles global actions and top-level links while the side menu handles section-level navigation
- Choose top submenu mode (
nds-top) when the navigation list is short (under 10 items) or the content area needs full viewport width on mobile - Choose slider mode (default) for longer navigation trees or when users frequently switch between pages and benefit from a persistent, independently scrollable menu
- Do not use the side menu for simple linear flows or single-page sites. Use Stepper for sequential processes or Tabs for switching between views on the same page
- Use two levels for most navigation trees. Use the three-level grouped pattern (with
nds-drawer-group) only when the section has enough items to benefit from categorization, such as a large component library - Always set
data-state="active"on the current page's<li>so users can orient themselves - Group related pages under a single accordion parent with a clear category label. Avoid mixing unrelated items in the same group
- Add
nds-cardViewto the layout wrapper when the page design calls for rounded, card-like containers. This applies border-radius to the sidebar - Hide the side menu entirely with
sidemenu_mode: falsein front matter on pages that do not need section navigation (landing pages, full-width dashboards)
Modifier Classes
| Class | Applied To | Description |
|---|---|---|
nds-top |
.nds-sidemenu |
Switches mobile behavior from slide-in sidebar to top dropdown bar |
nds-cardView |
.nds-content-layout |
Adds border-radius to the sidebar for a card-like appearance |
nds-peek |
.nds-sidemenu-toggle |
Enables the proximity-aware peek animation on the toggle button |
nds-divided |
.nds-drawer |
Adds separator lines between menu items (from the drawer component) |
nds-full-height |
.nds-drawer |
Stretches the drawer to fill the sidebar height |
Data Attributes
| Attribute | Description |
|---|---|
data-state="active" |
Set on <li> to mark the current page. Parent accordion groups expand automatically on initialization. |
data-state="open" |
Managed by JS on accordion groups and the sidebar itself. Indicates the element is expanded or visible. |
aria-expanded |
Set on accordion group buttons. Updated automatically by JS when groups expand or collapse. |
CSS Custom Properties
| Property | Default | Description |
|---|---|---|
--nds-sidemenu-width |
Set in variables | Width of the sidebar panel |
--drawer-max-height |
calc(100svh - nav - spacing) |
Maximum height of the drawer before scroll overflow activates. Recalculated dynamically in slider mode. |
--toggle-height |
40px |
Height of the toggle button |
--toggle-pos |
40svh |
Vertical position of the floating toggle button in slider mode |
JavaScript API
The NDS.Sidemenu namespace exposes a single initialization method. The component auto-initializes when .nds-sidemenu is present on the page, but you can call init() manually after dynamic content changes.
// ── Initialization ──────────────────────────────────
// Auto-runs on page load via nds-loader when .nds-sidemenu exists.
// Call manually after injecting a new sidemenu into the DOM:
NDS.Sidemenu.init();
// ── What init() sets up ─────────────────────────────
// - Toggle button: shown on tablet/mobile, opens/closes
// the sidebar with backdrop overlay
// - Close triggers: click outside, Escape key, or
// viewport width change all close the menu
// - Peek behavior: the toggle button flashes on load
// and reappears when the cursor moves near it
//
// Accordion toggle and active state expansion are handled
// by the drawer component (NDS.Drawer) automatically.
// ── Backdrop integration ────────────────────────────
// Uses NDS.Backdrop.show() / .hide() for the overlay.
// In slider mode, body scroll remains unlocked.
// In top submenu mode, body scroll locks while open.
// ── Drawer overflow ─────────────────────────────────
// Handled automatically by the .nds-scroll-more wrapper
// inside the drawer (shows/hides the "show more" button
// and edge mask-fade via its own ResizeObserver).