v1.6.x-dev NDS IQ v6

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

<nav class="nds-drawer"> <div class="nds-scroll-more nds-divided"> <ul class="nds-drawer-list nds-scroll-more-content"> <li data-state="active"> <a href="#" class="nds-btn nds-subtle nds-indicator"> <span class="nds-featured-icon nds-sm"> <i class="hgi hgi-stroke hgi-home-01"></i> </span> <span class="nds-label">Dashboard</span> </a> </li> <li> <button class="nds-btn nds-subtle nds-menu-btn nds-indicator" aria-expanded="false"> <span class="nds-featured-icon nds-sm"> <i class="hgi hgi-stroke hgi-layout-grid"></i> </span> <span class="nds-label">Components</span> </button> <ul> <li> <button class="nds-btn nds-subtle nds-menu-btn nds-indicator" aria-expanded="false"> <span class="nds-label">Buttons</span> </button> <ul> <li> <a href="#" class="nds-btn nds-subtle nds-indicator"> <span class="nds-label">Primary</span> </a> </li> <li> <a href="#" class="nds-btn nds-subtle nds-indicator"> <span class="nds-label">Secondary</span> </a> </li> <li> <a href="#" class="nds-btn nds-subtle nds-indicator"> <span class="nds-label">Danger</span> </a> </li> </ul> </li> <li> <a href="#" class="nds-btn nds-subtle nds-indicator"> <span class="nds-label">Cards</span> </a> </li> <li> <a href="#" class="nds-btn nds-subtle nds-indicator"> <span class="nds-label">Forms</span> </a> </li> </ul> </li> <li> <button class="nds-btn nds-subtle nds-menu-btn nds-indicator" aria-expanded="false"> <span class="nds-featured-icon nds-sm"> <i class="hgi hgi-stroke hgi-settings-01"></i> </span> <span class="nds-label">Settings</span> </button> <ul> <li> <a href="#" class="nds-btn nds-subtle nds-indicator"> <span class="nds-label">Profile</span> </a> </li> <li> <a href="#" class="nds-btn nds-subtle nds-indicator"> <span class="nds-label">Security</span> </a> </li> </ul> </li> <li> <a href="#" class="nds-btn nds-subtle nds-indicator"> <span class="nds-featured-icon nds-sm"> <i class="hgi hgi-stroke hgi-help-circle"></i> </span> <span class="nds-label">Help</span> </a> </li> </ul> </div> </nav>

Responsive State

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

<nav class="nds-drawer nds-divided" data-always-open-on="tablet"> <div class="nds-scroll-more nds-divided"> <ul class="nds-drawer-list nds-scroll-more-content"> <li> <button class="nds-btn nds-subtle nds-menu-btn nds-indicator"> <span class="nds-label">Components</span> </button> <ul> <li> <a href="#" class="nds-btn nds-subtle nds-indicator"> <span class="nds-label">Buttons</span> </a> </li> <li> <a href="#" class="nds-btn nds-subtle nds-indicator"> <span class="nds-label">Forms</span> </a> </li> </ul> </li> <li> <button class="nds-btn nds-subtle nds-menu-btn nds-indicator"> <span class="nds-label">Guidelines</span> </button> <ul> <li> <a href="#" class="nds-btn nds-subtle nds-indicator"> <span class="nds-label">Typography</span> </a> </li> <li> <a href="#" class="nds-btn nds-subtle nds-indicator"> <span class="nds-label">Colors</span> </a> </li> </ul> </li> </ul> </div> </nav>

Constrained Drawer

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

<nav class="nds-drawer nds-divided" style="--drawer-max-height: 200px;"> <div class="nds-scroll-more nds-divided"> <ul class="nds-drawer-list nds-scroll-more-content"> <li> <a href="#" class="nds-btn nds-subtle nds-indicator"> <span class="nds-featured-icon nds-sm"> <i class="hgi hgi-stroke hgi-promotion"></i> </span> <span class="nds-label">Item 1</span> </a> </li> <li> <a href="#" class="nds-btn nds-subtle nds-indicator"> <span class="nds-featured-icon nds-sm"> <i class="hgi hgi-stroke hgi-promotion"></i> </span> <span class="nds-label">Item 2</span> </a> </li> <li data-state="active"> <a href="#" class="nds-btn nds-subtle nds-indicator"> <span class="nds-featured-icon nds-sm"> <i class="hgi hgi-stroke hgi-promotion"></i> </span> <span class="nds-label">Item 3</span> </a> </li> <li> <a href="#" class="nds-btn nds-subtle nds-indicator"> <span class="nds-featured-icon nds-sm"> <i class="hgi hgi-stroke hgi-promotion"></i> </span> <span class="nds-label">Item 4</span> </a> </li> <li> <a href="#" class="nds-btn nds-subtle nds-indicator"> <span class="nds-featured-icon nds-sm"> <i class="hgi hgi-stroke hgi-promotion"></i> </span> <span class="nds-label">Item 5</span> </a> </li> <li> <a href="#" class="nds-btn nds-subtle nds-indicator"> <span class="nds-featured-icon nds-sm"> <i class="hgi hgi-stroke hgi-promotion"></i> </span> <span class="nds-label">Item 6</span> </a> </li> </ul> <button class="nds-btn nds-subtle nds-md nds-show-more" type="button" aria-label="Show more"> <span class="nds-label">Show more</span> <i class="nds-icon nds-hgi-arrow-down-01" aria-hidden="true"></i> </button> </div> </nav>

Fit Mode

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

Latest Updates

<div class="nds-grid" style="--max-col: 2;--mid-col: 1;--min-col: 1;--row-gap: var(--spacing-6xl);"> <div class="nds-flex nds-col" style="--align: start;--gap: 0;"> <h4>Latest Updates</h4> <nav class="nds-drawer nds-divided nds-fit nds-card nds-stroke" style="--drawer-max-height:100%; --drawer-truncate:2;"> <div class="nds-scroll-more nds-divided"> <ul class="nds-drawer-list nds-scroll-more-content"> <li> <button class="nds-btn nds-subtle nds-indicator"> <span class="nds-label nds-truncate">New digital identity verification system now available</span> <span class="nds-tag nds-gray nds-xs"> <span class="nds-label">23/12/2025</span> </span> </button> </li> <li> <button class="nds-btn nds-subtle nds-indicator"> <span class="nds-label nds-truncate">Enhanced online portal for business licensing and permits</span> <span class="nds-tag nds-gray nds-xs"> <span class="nds-label">18/12/2025</span> </span> </button> </li> <li> <button class="nds-btn nds-subtle nds-indicator"> <span class="nds-label nds-truncate">Open data initiative: government datasets now publicly accessible</span> <span class="nds-tag nds-gray nds-xs"> <span class="nds-label">18/12/2025</span> </span> </button> </li> <li> <button class="nds-btn nds-subtle nds-indicator"> <span class="nds-label nds-truncate">National cybersecurity awareness campaign launches</span> <span class="nds-tag nds-gray nds-xs"> <span class="nds-label">17/12/2025</span> </span> </button> </li> </ul> <button class="nds-btn nds-subtle nds-md nds-show-more" type="button" aria-label="Show more"> <i class="nds-icon nds-hgi-arrow-down-01" aria-hidden="true"></i> </button> </div> </nav> </div> <div class="nds-flex nds-col" style="--align: start;--gap: 0;"> <h4>Quick Links</h4> <nav class="nds-drawer nds-divided nds-fit nds-card nds-stroke" style="--drawer-max-height:100%; --drawer-truncate:2;"> <div class="nds-scroll-more nds-divided"> <ul class="nds-drawer-list nds-scroll-more-content"> <li> <a class="nds-btn nds-subtle nds-indicator" href="#"> <span class="nds-featured-icon nds-sm"> <i class="hgi hgi-stroke hgi-link-02"></i> </span> <span class="nds-label nds-truncate">Services Portal</span> <i class="nds-icon nds-hgi-arrow-next-01"></i> </a> </li> <li> <a class="nds-btn nds-subtle nds-indicator" href="#"> <span class="nds-featured-icon nds-sm"> <i class="hgi hgi-stroke hgi-link-02"></i> </span> <span class="nds-label nds-truncate">Laws and Regulations</span> <i class="hgi hgi-stroke hgi-link-square-02"></i> </a> </li> <li> <a class="nds-btn nds-subtle nds-indicator" href="#"> <span class="nds-featured-icon nds-sm"> <i class="hgi hgi-stroke hgi-link-02"></i> </span> <span class="nds-label nds-truncate">Careers</span> <i class="nds-icon nds-hgi-arrow-next-01"></i> </a> </li> <li> <a class="nds-btn nds-subtle nds-indicator" href="#"> <span class="nds-featured-icon nds-sm"> <i class="hgi hgi-stroke hgi-link-02"></i> </span> <span class="nds-label nds-truncate">Open Data</span> <i class="hgi hgi-stroke hgi-link-square-02"></i> </a> </li> <li> <a class="nds-btn nds-subtle nds-indicator" href="#"> <span class="nds-featured-icon nds-sm"> <i class="hgi hgi-stroke hgi-link-02"></i> </span> <span class="nds-label nds-truncate">Media Center</span> <i class="nds-icon nds-hgi-arrow-next-01"></i> </a> </li> <li> <a class="nds-btn nds-subtle nds-indicator" href="#"> <span class="nds-featured-icon nds-sm"> <i class="hgi hgi-stroke hgi-link-02"></i> </span> <span class="nds-label nds-truncate">Contact Directory</span> <i class="hgi hgi-stroke hgi-link-square-02"></i> </a> </li> </ul> <button class="nds-btn nds-subtle nds-md nds-show-more" type="button" aria-label="Show more"> <i class="nds-icon nds-hgi-arrow-down-01" aria-hidden="true"></i> </button> </div> </nav> </div> </div>

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
<div class="nds-flex nds-col" style="--gap: 0;"> <nav class="nds-drawer" style="--drawer-max-height: 360px;"> <div class="nds-scroll-more nds-divided"> <ul class="nds-drawer-list nds-scroll-more-content"> <li data-status="success"> <a href="#" class="nds-btn nds-subtle nds-indicator"> <span class="nds-featured-icon nds-sm"> <i class="hgi hgi-stroke hgi-checkmark-circle-01"></i> </span> <span class="nds-drawer-item"> <span class="nds-drawer-item-head"> <span class="nds-tag nds-sm" data-status="success"> <span class="nds-label">success</span> </span> <span class="nds-label nds-truncate">Document Approved</span> </span> <span class="nds-description">Your business license application has been reviewed and approved by the licensing authority.</span> </span> </a> </li> <li data-status="warning"> <a href="#" class="nds-btn nds-subtle nds-indicator"> <span class="nds-featured-icon nds-sm"> <i class="hgi hgi-stroke hgi-alert-02"></i> </span> <span class="nds-drawer-item"> <span class="nds-drawer-item-head"> <span class="nds-tag nds-sm" data-status="warning"> <span class="nds-label">warning</span> </span> <span class="nds-label nds-truncate">Payment Overdue</span> </span> <span class="nds-description">Your annual business registration fee of 1,200 SAR is past due. Complete payment to avoid service suspension.</span> </span> </a> </li> <li data-status="info"> <a href="#" class="nds-btn nds-subtle nds-indicator"> <span class="nds-featured-icon nds-sm"> <i class="hgi hgi-stroke hgi-notification-02"></i> </span> <span class="nds-drawer-item"> <span class="nds-drawer-item-head"> <span class="nds-tag nds-sm" data-status="info"> <span class="nds-label">info</span> </span> <span class="nds-label nds-truncate">System Maintenance</span> </span> <span class="nds-description">Scheduled maintenance window from 2:00 AM to 4:00 AM on Friday. Some services may be unavailable.</span> </span> </a> </li> <li data-status="error"> <a href="#" class="nds-btn nds-subtle nds-indicator"> <span class="nds-featured-icon nds-sm"> <i class="hgi hgi-stroke hgi-shield-01"></i> </span> <span class="nds-drawer-item"> <span class="nds-drawer-item-head"> <span class="nds-tag nds-sm" data-status="error"> <span class="nds-label">error</span> </span> <span class="nds-label nds-truncate">Login Attempt Blocked</span> </span> <span class="nds-description">An unauthorized login attempt was detected from an unrecognized device. Your account has been temporarily locked.</span> </span> </a> </li> <li data-status="info"> <a href="#" class="nds-btn nds-subtle nds-indicator"> <span class="nds-featured-icon nds-sm"> <i class="hgi hgi-stroke hgi-mail-01"></i> </span> <span class="nds-drawer-item"> <span class="nds-drawer-item-head"> <span class="nds-tag nds-sm" data-status="info"> <span class="nds-label">info</span> </span> <span class="nds-label nds-truncate">New Message Received</span> </span> <span class="nds-description">You have a new message from the Ministry of Commerce regarding your trade license renewal.</span> </span> </a> </li> </ul> </div> </nav> <hr class="nds-divider"> <a href="#" class="nds-btn nds-subtle nds-full"> <i class="hgi hgi-stroke hgi-notification-02"></i> <span class="nds-label">View all notifications</span> </a> </div>

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.

Empty State

A drawer whose list can run out of items (notifications, search results, a filtered menu) takes nds-empty on the <ul class="nds-drawer-list">: the Empty component fills it with a placeholder while it holds no items and clears it as soon as one arrives. Set the wording and icon with data-empty-message and data-empty-icon on the same list.

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-lg Roomier size with taller items (48px) and wider indentation (--spacing-2xl).
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.
element: li > ul > li No class needed. A li directly inside a sub-ul is automatically treated as a 3-level group header: the element selector restores display: flex, full text color, and applies semibold weight to its direct button. Nest another ul inside to create the collapsible sub-list.
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 calc(var(--spacing-xl)) Submenu indentation, derived from the button's inline padding. Overridden to --spacing-2xl by 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.
--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 1px 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
Last Modified Date: 04/08/2026 - 04:02 PM
Was this page useful?
60% of users said Yes from 2843 Feedbacks