Drawer - National Design System

A vertical list container for sidebar navigation, submenus, quick links, and inline notifications, with compact or expanded layouts that adapt across breakpoints.

Nested Menu

Accordion-style navigation with expandable submenus and active state indicators

Responsive State

Permanently expanded on tablet and above, collapsible with toggle arrows on mobile

Constrained Drawer

Height-limited drawer with scroll container and show-more button for overflow

Fit Mode

Stretches to fill parent height for equal-height columns in grid layouts

Latest Updates

Rich List Items

List items with icon, title, status tag, and a description on a second row. Status on the <li data-status> cascades to the featured icon and tag automatically.

Notifications with status cascade

Built-in Features

Auto-initialization

Works on page load with just HTML markup. For dynamically added drawers, call reinit() to activate new instances.

Accordion Submenus

Smooth animated expand and collapse with automatic sibling closing. Submenus nest to any depth and inherit the same animation, indicator, and state tracking at every level.

State Management

Open, closing, and active states tracked via data-state with custom events on every transition. Active nested items auto-reveal their parent menus on load.

Breakpoint-Driven Modes

Static expanded list on desktop that collapses into an accordion on mobile, controlled per-drawer or per-item with a single data attribute.

Scroll Overflow

Height-constrained mode with gradient fade and a show-more button that auto-detects overflow and flips at scroll end.

JavaScript API

Programmatic toggle, overflow check, init, destroy, and custom events fired on every submenu open and close.

Usage Guidelines

Best Practices

  • Use drawers for sidebar navigation with nested menu structures. The accordion behavior keeps the interface tidy by closing siblings automatically when a new submenu opens.
  • Works equally well for flat link lists like quick links, latest updates, promotional items, or related resources where no nesting is needed.
  • Use the Rich List Items layout for inline notification feeds, activity lists, or any list where each row needs an icon, title, status tag, and a supporting description line.
  • Do not use a drawer for top-level page navigation. Use the Header instead, which is built to host primary nav, search, and user controls.
  • Do not use a drawer for compact action menus attached to a single trigger. Use the Dropmenu, which handles positioning and dismissal for overlay menus.
  • Reach for nds-fit when the drawer sits in a grid column that should match the height of sibling cards. Combine with nds-card and nds-stroke for a framed block.
  • Add nds-divided when rows carry multi-line content (descriptions, timestamps, tags). Leave it off for dense flat link lists where the indicator alone is enough visual separation.
  • Submenus nest to any depth, but keep trees shallow (two or three levels) for readability. Deeper structures read like a tangled outline and often hint at a page that should be split up.
  • Mark the current page with data-state="active" on the deepest <li>. The drawer automatically opens all ancestor submenus on load so users land with the active path revealed.
  • Constrain long lists by setting --drawer-max-height on the <nav> and wrapping the list in .nds-scroll-more. The fade mask and show-more button appear only when content actually overflows.
  • Apply nds-oncolor when the drawer sits on a dark or tinted surface. It rebalances dividers and indicators so rows remain legible without touching text colors.

Modifier Classes

Class Description
nds-sm Compact size with tighter indentation (--spacing-lg) and a 2px active indicator.
nds-lg Roomier size with wider indentation (--spacing-2xl) and a 4px active indicator.
nds-divided Adds horizontal divider lines between list items. Useful for rows with multi-line content.
nds-fit Stretches the drawer to fill its parent height for equal-height columns in grid layouts.
nds-oncolor Rebalances dividers and indicators for use on dark or colored backgrounds.
nds-card Displays the drawer as a card-width block, letting it sit alongside other cards in a grid.
nds-lined Adds a vertical side rail (::before pseudo-element) alongside every sub-list. Opt-in — add to .nds-drawer. Used by the site sidemenu and TOC. Rail width and block-axis inset are controlled by --drawer-lined-width and --drawer-lined-block.
nds-drawer-group Applied to a li inside a sub-ul to create a 3-level collapsible group. The group renders as a bold header button that expands a nested sub-list. Restores display: flex and full text color that the muted sub-list styles normally suppress.
nds-divided on ul Applied to an inner sub-ul (not the drawer root) to add border-block-end dividers between its immediate children. The last child never gets a divider. Useful for separating group items within a 3-level drawer.

Data Attributes

Attribute Description
data-state="active" Set on <li> to mark the current page. The button indicator activates and parent menus expand automatically.
data-open-on Set on drawer or individual <li>. Submenus start expanded at the matching breakpoint but remain toggleable. tablet opens on tablet and everything larger. tablet-max opens on tablet and everything smaller, closed on desktop. Values: mobile, tablet, tablet-max, desktop, desktop-max, large-desktop, always, never.
data-always-open-on Set on drawer only. Locks all submenus open and disables interaction at the matching breakpoint. Arrows hidden, buttons non-clickable. Below that breakpoint, reverts to normal accordion. Same values as data-open-on.

CSS Custom Properties

Property Default Description
--drawer-gap 0px Vertical spacing between list items.
--drawer-indent var(--spacing-md) Submenu indentation. Increases with nds-sm and nds-lg.
--drawer-divider var(--divider-color) Color of the divider lines when nds-divided is applied.
--drawer-indicator-width 5px Thickness of the active/hover indicator bar. Overridden by nds-sm (2px) and nds-lg (4px).
--drawer-indicator transparent Default indicator color for inactive items.
--drawer-indicator-active var(--background-primary) Indicator color for the active item (data-state="active").
--drawer-indicator-hover var(--colors-neutral-400) Indicator color on hover.
--drawer-truncate 1 Maximum number of visible lines per label before truncation.
--drawer-transition var(--nds-transition) Animation timing for submenu expand and collapse.
--drawer-max-height 400px Maximum height before scroll overflow activates (requires .nds-scroll-more wrapper).
--drawer-btn-height fit-content Height applied to each row button. Set to 100% when nds-fit is used.
--drawer-btn-gap var(--spacing-md) Gap between the icon, label, and trailing content inside a row.
--drawer-lined-width 2px Width of the vertical side rail rendered by nds-lined.
--drawer-lined-block 0px Block-axis inset (top and bottom trim) of the side rail. Increase to shorten the rail so it does not reach the very top or bottom edge of its sub-list.

JavaScript API

NDS.Drawer initializes automatically on page load for all .nds-drawer elements. For dynamically added drawers, call NDS.Drawer.create(element) (legacy alias: NDS.Drawer.initDrawer).

// ── Initialize ────────────────────────────────────── NDS.Drawer.init(); // All drawers on the page NDS.Drawer.reinit(); // Re-scan (same as init) NDS.Drawer.create(drawerEl); // Single drawer element (alias: initDrawer) // ── Toggle a submenu ──────────────────────────────── // Pass the parent button element (does nothing in always-open mode) const menuBtn = drawer.querySelector('.nds-menu-btn'); NDS.Drawer.toggle(menuBtn); // ── Destroy ───────────────────────────────────────── // Removes event listeners, resize observers, and stored state NDS.Drawer.destroy(drawerEl); // ── Custom Events ─────────────────────────────────── // Fired on the drawer element, bubbles up document.addEventListener('nds:drawer:shown', (e) => { console.log('Submenu opened:', e.detail.item); // The <li> element console.log('Drawer:', e.detail.drawer); // The .nds-drawer element }); document.addEventListener('nds:drawer:hidden', (e) => { console.log('Submenu closed:', e.detail.item); }); // ── Responsive Attributes ─────────────────────────── // data-open-on: 'mobile' | 'tablet' | 'tablet-max' | 'desktop' | 'desktop-max' | 'large-desktop' | 'always' | 'never' // Set on drawer (default for all items) or on individual <li> (override) // data-always-open-on: same breakpoint values // Makes drawer permanently expanded and non-interactive at that breakpoint
Was this page useful?
60% of users said Yes from 2843 Feedbacks