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.
Basic Usage
Basic Drawer
html
<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
html
<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
html
<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
html
<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
html
<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
html
<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
html
<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
html
<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
html
<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
html
<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
html
<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
html
<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
html
<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
html
<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
html
<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
html
<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
html
<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
html
<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
html
<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
html
<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
html
<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
html
<!-- 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