Drawer
Material Design 3 navigation drawer component for side navigation
Drawer
Navigation drawers provide access to destinations in your app. @duskmoon-dev/core provides a flexible drawer component with multiple variants and positions.
Native non-modal drawer
Use popover="auto" for a non-modal surface in the browser’s top layer. The browser handles Escape, outside-click dismissal, and focus restoration when dismissed with Escape. It does not make the rest of the page inert or trap focus.
Native non-modal Drawer
<button type="button" class="btn" command="toggle-popover" commandfor="demo-drawer-popover">Open drawer</button>
<aside id="demo-drawer-popover" class="drawer drawer-left" popover="auto" aria-labelledby="demo-drawer-title">
<div class="drawer-header">
<h2 id="demo-drawer-title" class="drawer-title">Quick actions</h2>
<button type="button" class="drawer-close" command="hide-popover" commandfor="demo-drawer-popover" aria-label="Close drawer" autofocus>×</button>
</div>
<div class="drawer-body">
<p>Choose an action, or keep working on the page.</p>
</div>
</aside>Native modal drawer
Use a native dialog with show-modal for modal interaction: the browser makes the background inert, keeps keyboard focus inside, and returns focus to the opener on close. closedby="any" enables outside-click dismissal in supporting browsers; Escape and the explicit close button remain available. Omit closedby="any" to require Escape or a close action. The scrim comes from ::backdrop; no separate backdrop element is needed.
Native modal Drawer
<button type="button" class="btn btn-primary" command="show-modal" commandfor="demo-drawer-modal">Open modal drawer</button>
<dialog id="demo-drawer-modal" class="drawer drawer-left" closedby="any" aria-labelledby="demo-drawer-modal-title">
<div class="drawer-header">
<h2 id="demo-drawer-modal-title" class="drawer-title">Review changes</h2>
<button type="button" class="drawer-close" command="close" commandfor="demo-drawer-modal" aria-label="Close drawer" autofocus>×</button>
</div>
<div class="drawer-body">
<p>Review your changes before continuing.</p>
</div>
</dialog>State, positioning, and motion
Native surfaces use :popover-open or the dialog’s open attribute. Do not add legacy visibility classes or toggle open directly for modal dialogs. Invoker commands require browser support; applications targeting older browsers can call showPopover() / hidePopover() and showModal() / close() as a compatibility fallback.
These panels attach to viewport edges rather than their trigger, so they do not need an anchor name. Opening and closing transitions preserve the top layer with discrete display / overlay transitions. Reduced-motion preferences disable these transitions.
Legacy class-controlled examples
The following examples retain the existing class API. Applications using it must manage focus, Escape, and dismissal themselves. Prefer the native examples above for overlays.
Basic Usage
Basic Drawer
<div class="drawer drawer-left drawer-open">
<div class="drawer-header">
<h2 class="drawer-title">Menu</h2>
<button class="drawer-close" aria-label="Close drawer">×</button>
</div>
<div class="drawer-body">
<a href="#" class="drawer-item drawer-item-active">
<span class="drawer-item-icon">🏠</span>
Home
</a>
<a href="#" class="drawer-item">
<span class="drawer-item-icon">⚙️</span>
Settings
</a>
</div>
</div>
<!-- Backdrop -->
<div class="drawer-backdrop drawer-backdrop-show"></div>Position
Left Drawer (Default)
Left Drawer
<div class="drawer drawer-left drawer-open">
<div class="drawer-body">
<a href="#" class="drawer-item">Dashboard</a>
<a href="#" class="drawer-item">Profile</a>
<a href="#" class="drawer-item">Settings</a>
</div>
</div>Right Drawer
Right Drawer
<div class="drawer drawer-right drawer-open">
<div class="drawer-body">
<a href="#" class="drawer-item">Notifications</a>
<a href="#" class="drawer-item">Messages</a>
<a href="#" class="drawer-item">Help</a>
</div>
</div>Surface Variants
Surface Container Low (Default)
Surface Container Low
<div class="drawer drawer-left drawer-surface-container-low">
<div class="drawer-body">
<a href="#" class="drawer-item">Item 1</a>
<a href="#" class="drawer-item">Item 2</a>
</div>
</div>Surface
Surface Variant
<div class="drawer drawer-left drawer-surface">
<div class="drawer-body">
<a href="#" class="drawer-item">Item 1</a>
<a href="#" class="drawer-item">Item 2</a>
</div>
</div>Surface Container
Surface Container
<div class="drawer drawer-left drawer-surface-container">
<div class="drawer-body">
<a href="#" class="drawer-item">Item 1</a>
<a href="#" class="drawer-item">Item 2</a>
</div>
</div>Surface Container High
Surface Container High
<div class="drawer drawer-left drawer-surface-container-high">
<div class="drawer-body">
<a href="#" class="drawer-item">Item 1</a>
<a href="#" class="drawer-item">Item 2</a>
</div>
</div>Sizes
Small
Small Drawer
<div class="drawer drawer-left drawer-sm">
<div class="drawer-body">
<a href="#" class="drawer-item">Item 1</a>
<a href="#" class="drawer-item">Item 2</a>
</div>
</div>Medium (Default)
Medium Drawer
<div class="drawer drawer-left drawer-md">
<div class="drawer-body">
<a href="#" class="drawer-item">Item 1</a>
<a href="#" class="drawer-item">Item 2</a>
</div>
</div>Large
Large Drawer
<div class="drawer drawer-left drawer-lg">
<div class="drawer-body">
<a href="#" class="drawer-item">Item 1</a>
<a href="#" class="drawer-item">Item 2</a>
</div>
</div>Extra Large
Extra Large Drawer
<div class="drawer drawer-left drawer-xl">
<div class="drawer-body">
<a href="#" class="drawer-item">Item 1</a>
<a href="#" class="drawer-item">Item 2</a>
</div>
</div>Rail Variant
Collapsed icon-only drawer:
Rail Drawer
<div class="drawer drawer-left drawer-rail drawer-open">
<div class="drawer-body">
<a href="#" class="drawer-item drawer-item-active">
<span class="drawer-item-icon">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" />
</svg>
</span>
Home
</a>
<a href="#" class="drawer-item">
<span class="drawer-item-icon">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
</svg>
</span>
Profile
</a>
</div>
</div>Permanent Drawer
Always visible, no overlay:
Permanent Drawer
<div class="drawer drawer-left drawer-permanent">
<div class="drawer-header">
<h2 class="drawer-title">Navigation</h2>
</div>
<div class="drawer-body">
<a href="#" class="drawer-item drawer-item-active">Dashboard</a>
<a href="#" class="drawer-item">Projects</a>
<a href="#" class="drawer-item">Team</a>
</div>
</div>With Sections and Labels
Drawer with Sections
<div class="drawer drawer-left drawer-open">
<div class="drawer-body">
<div class="drawer-label">Main</div>
<a href="#" class="drawer-item drawer-item-active">Dashboard</a>
<a href="#" class="drawer-item">Analytics</a>
<div class="drawer-divider"></div>
<div class="drawer-label">Settings</div>
<a href="#" class="drawer-item">Profile</a>
<a href="#" class="drawer-item">Preferences</a>
</div>
</div>With Icons and Badges
Drawer with Icons and Badges
<div class="drawer drawer-left drawer-open">
<div class="drawer-body">
<a href="#" class="drawer-item drawer-item-active">
<span class="drawer-item-icon">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" />
</svg>
</span>
Home
</a>
<a href="#" class="drawer-item">
<span class="drawer-item-icon">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
</svg>
</span>
Messages
<span class="drawer-item-badge">5</span>
</a>
<a href="#" class="drawer-item">
<span class="drawer-item-icon">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" />
</svg>
</span>
Notifications
<span class="drawer-item-badge">12</span>
</a>
</div>
</div>Active Item Colors
Primary (Default)
Active Primary
<div class="drawer drawer-left">
<div class="drawer-body">
<a href="#" class="drawer-item drawer-item-active-primary">Active Primary</a>
<a href="#" class="drawer-item">Item 2</a>
</div>
</div>Secondary
Active Secondary
<div class="drawer drawer-left">
<div class="drawer-body">
<a href="#" class="drawer-item drawer-item-active-secondary">Active Secondary</a>
<a href="#" class="drawer-item">Item 2</a>
</div>
</div>Tertiary
Active Tertiary
<div class="drawer drawer-left">
<div class="drawer-body">
<a href="#" class="drawer-item drawer-item-active-tertiary">Active Tertiary</a>
<a href="#" class="drawer-item">Item 2</a>
</div>
</div>Complete Drawer with Header and Footer
Complete Drawer
<div class="drawer drawer-left drawer-open">
<div class="drawer-header">
<h2 class="drawer-title">App Name</h2>
<button class="drawer-close" aria-label="Close menu">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
<div class="drawer-body">
<div class="drawer-label">Navigation</div>
<a href="#" class="drawer-item drawer-item-active">
<span class="drawer-item-icon">📊</span>
Dashboard
</a>
<a href="#" class="drawer-item">
<span class="drawer-item-icon">📁</span>
Projects
</a>
<a href="#" class="drawer-item">
<span class="drawer-item-icon">👥</span>
Team
</a>
<div class="drawer-divider"></div>
<div class="drawer-label">Settings</div>
<a href="#" class="drawer-item">
<span class="drawer-item-icon">⚙️</span>
Settings
</a>
</div>
<div class="drawer-footer">
<button class="btn btn-outlined btn-sm">Logout</button>
</div>
</div>
<div class="drawer-backdrop drawer-backdrop-show"></div>Nested Menu Items
Nested Menu Items
<div class="drawer drawer-left drawer-open">
<div class="drawer-body">
<a href="#" class="drawer-item drawer-item-active">Dashboard</a>
<a href="#" class="drawer-item">Projects</a>
<a href="#" class="drawer-item drawer-item-nested">Project A</a>
<a href="#" class="drawer-item drawer-item-nested">Project B</a>
<a href="#" class="drawer-item drawer-item-nested-2">Subproject B1</a>
<a href="#" class="drawer-item">Settings</a>
</div>
</div>Best Practices
Navigation Structure
Organize drawer items logically:
- Primary navigation at the top
- Group related items with labels and dividers
- Settings/profile at the bottom
Accessibility
- Use semantic elements (
<nav>,<a>,<button>) - Provide
aria-labelfor close buttons - Support keyboard navigation
- Include focus indicators
Accessible Drawer
<nav class="drawer drawer-left drawer-open" aria-label="Main navigation">
<div class="drawer-header">
<h2 class="drawer-title">Menu</h2>
<button class="drawer-close" aria-label="Close navigation menu">×</button>
</div>
<div class="drawer-body">
<a href="#" class="drawer-item" aria-current="page">Home</a>
<a href="#" class="drawer-item">Settings</a>
</div>
</nav>Responsive Design
Use permanent drawer for desktop, modal drawer for mobile:
Responsive Drawer
<!-- Desktop: permanent drawer -->
<div class="drawer drawer-left drawer-permanent hidden lg:flex">
<div class="drawer-body">
<a href="#" class="drawer-item">Dashboard</a>
<a href="#" class="drawer-item">Settings</a>
</div>
</div>
<!-- Mobile: modal drawer with backdrop -->
<div class="drawer drawer-left lg:hidden" id="mobile-drawer">
<div class="drawer-body">
<a href="#" class="drawer-item">Dashboard</a>
<a href="#" class="drawer-item">Settings</a>
</div>
</div>
<div class="drawer-backdrop lg:hidden"></div>Framework Examples
React
import { useState } from 'react';
interface DrawerProps {
open: boolean;
onClose: () => void;
position?: 'left' | 'right';
children: React.ReactNode;
}
export function Drawer({ open, onClose, position = 'left', children }: DrawerProps) {
return (
<>
<div className={`drawer drawer-${position} ${open ? 'drawer-open' : ''}`}>
<div className="drawer-header">
<h2 className="drawer-title">Menu</h2>
<button className="drawer-close" onClick={onClose} aria-label="Close">
×
</button>
</div>
<div className="drawer-body">{children}</div>
</div>
<div
className={`drawer-backdrop ${open ? 'drawer-backdrop-show' : ''}`}
onClick={onClose}
/>
</>
);
}
// Usage
function App() {
const [open, setOpen] = useState(false);
return (
<>
<button onClick={() => setOpen(true)}>Open Drawer</button>
<Drawer open={open} onClose={() => setOpen(false)}>
<a href="#" className="drawer-item">Home</a>
<a href="#" className="drawer-item">Settings</a>
</Drawer>
</>
);
}
Vue
<template>
<div>
<div :class="['drawer', `drawer-${position}`, { 'drawer-open': open }]">
<div class="drawer-header">
<h2 class="drawer-title">Menu</h2>
<button class="drawer-close" @click="$emit('close')" aria-label="Close">
×
</button>
</div>
<div class="drawer-body">
<slot />
</div>
</div>
<div
:class="['drawer-backdrop', { 'drawer-backdrop-show': open }]"
@click="$emit('close')"
/>
</div>
</template>
<script setup>
defineProps({
open: {
type: Boolean,
required: true
},
position: {
type: String,
default: 'left'
}
});
defineEmits(['close']);
</script>
<!-- Usage -->
<Drawer :open="isOpen" @close="isOpen = false">
<a href="#" class="drawer-item">Home</a>
<a href="#" class="drawer-item">Settings</a>
</Drawer>
JavaScript (Vanilla)
// Toggle drawer
function toggleDrawer(drawerId) {
const drawer = document.getElementById(drawerId);
const backdrop = document.querySelector('.drawer-backdrop');
drawer.classList.toggle('drawer-open');
backdrop.classList.toggle('drawer-backdrop-show');
}
// Close drawer when clicking backdrop
document.querySelector('.drawer-backdrop').addEventListener('click', () => {
document.querySelector('.drawer').classList.remove('drawer-open');
document.querySelector('.drawer-backdrop').classList.remove('drawer-backdrop-show');
});
// Close drawer button
document.querySelector('.drawer-close').addEventListener('click', () => {
document.querySelector('.drawer').classList.remove('drawer-open');
document.querySelector('.drawer-backdrop').classList.remove('drawer-backdrop-show');
});
API Reference
Drawer Classes
| Class | Description |
|---|---|
.drawer |
Base drawer container (required) |
.drawer-left |
Left-side drawer (default) |
.drawer-right |
Right-side drawer |
.drawer-open |
Open state |
.drawer-surface |
Surface background |
.drawer-surface-container |
Surface container background |
.drawer-surface-container-low |
Surface container low (default) |
.drawer-surface-container-high |
Surface container high |
.drawer-sm |
Small width (12rem) |
.drawer-md |
Medium width (16rem, default) |
.drawer-lg |
Large width (20rem) |
.drawer-xl |
Extra large width (24rem) |
.drawer-rail |
Rail variant (icon-only, 5rem) |
.drawer-permanent |
Permanent drawer (always visible) |
Content Classes
| Class | Description |
|---|---|
.drawer-header |
Header section |
.drawer-title |
Title text |
.drawer-body |
Main content area |
.drawer-footer |
Footer section |
.drawer-close |
Close button |
.drawer-label |
Section label |
.drawer-divider |
Horizontal divider |
.drawer-backdrop |
Backdrop overlay |
.drawer-backdrop-show |
Show backdrop |
Item Classes
| Class | Description |
|---|---|
.drawer-item |
Navigation item |
.drawer-item-active |
Active item (primary) |
.drawer-item-active-primary |
Active with primary color |
.drawer-item-active-secondary |
Active with secondary color |
.drawer-item-active-tertiary |
Active with tertiary color |
.drawer-item-icon |
Icon container |
.drawer-item-badge |
Badge indicator |
.drawer-item-nested |
First level nested item |
.drawer-item-nested-2 |
Second level nested item |
Related Components
- Navbar - Top navigation
- Menu - Dropdown menus
- Bottom Navigation - Mobile bottom navigation