Implement a new sidebar component based on DaisyUI Drawer pattern without custom CSS variants. The sidebar supports desktop (expanded/collapsed states) and mobile (overlay drawer) with full accessibility compliance. Sidebar Implementation: - Refactor sidebar component with sidebar_header, menu_item, menu_group, sidebar_footer sub-components - Add logo (mila.svg) with size-8 (32px) always visible - Implement toggle button with icon swap (chevron-left/right) for desktop - Add nested menu support with details/summary (expanded) and dropdown (collapsed) patterns - Implement footer with language selector (expanded-only), theme toggle, and user menu with avatar - Update layouts.ex to use drawer pattern with data-sidebar-expanded attribute for state management CSS & JavaScript: - Add CSS styles for sidebar state management via data-attribute selectors - Implement SidebarState JavaScript hook for localStorage persistence - Add smooth width transitions (w-64 ↔ w-16) for desktop collapsed state - Add CSS classes for expanded-only, menu-label, and icon visibility Documentation: - Add sidebar-analysis-current-state.md: Analysis of current implementation - Add sidebar-requirements-v2.md: Complete specification for new sidebar - Add daisyui-drawer-pattern.md: DaisyUI pattern documentation - Add umsetzung-sidebar.md: Step-by-step implementation guide Testing: - Add comprehensive component tests for all sidebar sub-components - Add integration tests for sidebar state management and mobile drawer - Extend accessibility tests (ARIA labels, roles, keyboard navigation) - Add regression tests for duplicate IDs, hover effects, and tooltips - Ensure full test coverage per specification requirements
15 KiB
DaisyUI Drawer Pattern - Standard Implementation
This document describes the standard DaisyUI drawer pattern for implementing responsive sidebars. It covers mobile overlay drawers, desktop persistent sidebars, and their combination.
Core Concept
DaisyUI's drawer component uses a checkbox-based toggle mechanism combined with CSS to create accessible, responsive sidebars without custom JavaScript.
Key Components
drawer- Container elementdrawer-toggle- Hidden checkbox that controls open/close statedrawer-content- Main content areadrawer-side- Sidebar content (menu, navigation)drawer-overlay- Optional overlay for mobile (closes drawer on click)
HTML Structure
<div class="drawer">
<!-- Hidden checkbox controls the drawer state -->
<input id="my-drawer" type="checkbox" class="drawer-toggle" />
<!-- Main content area -->
<div class="drawer-content">
<!-- Page content goes here -->
<label for="my-drawer" class="btn btn-primary">Open drawer</label>
</div>
<!-- Sidebar content -->
<div class="drawer-side">
<label for="my-drawer" aria-label="close sidebar" class="drawer-overlay"></label>
<ul class="menu p-4 w-80 min-h-full bg-base-200 text-base-content">
<!-- Sidebar content goes here -->
<li><a>Sidebar Item 1</a></li>
<li><a>Sidebar Item 2</a></li>
</ul>
</div>
</div>
How drawer-toggle Works
Mechanism
The drawer-toggle is a hidden checkbox that serves as the state controller:
<input id="my-drawer" type="checkbox" class="drawer-toggle" />
Toggle Behavior
- Label Connection: Any
<label for="my-drawer">element can toggle the drawer - Checkbox State:
checked→ drawer is openunchecked→ drawer is closed
- CSS Targeting: DaisyUI uses CSS sibling selectors to show/hide the drawer based on checkbox state
- Accessibility: Native checkbox provides keyboard accessibility (Space/Enter to toggle)
Toggle Examples
<!-- Button to open drawer -->
<label for="my-drawer" class="btn btn-primary drawer-button">
Open Menu
</label>
<!-- Close button inside drawer -->
<label for="my-drawer" class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2">✕</label>
<!-- Overlay to close (click outside) -->
<label for="my-drawer" aria-label="close sidebar" class="drawer-overlay"></label>
Mobile Drawer (Overlay)
Characteristics
- Drawer slides in from the side (usually left)
- Overlays the main content
- Dark overlay (drawer-overlay) behind drawer
- Clicking overlay closes the drawer
- Typically used on mobile/tablet screens
Implementation
<div class="drawer">
<input id="mobile-drawer" type="checkbox" class="drawer-toggle" />
<div class="drawer-content">
<!-- Toggle button in header -->
<div class="navbar bg-base-100">
<div class="flex-none">
<label for="mobile-drawer" class="btn btn-square btn-ghost">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" class="inline-block w-5 h-5 stroke-current">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path>
</svg>
</label>
</div>
<div class="flex-1">
<a class="btn btn-ghost text-xl">My App</a>
</div>
</div>
<!-- Main content -->
<div class="p-4">
<h1>Main Content</h1>
</div>
</div>
<div class="drawer-side">
<!-- Overlay - clicking it closes the drawer -->
<label for="mobile-drawer" aria-label="close sidebar" class="drawer-overlay"></label>
<!-- Sidebar menu -->
<ul class="menu p-4 w-80 min-h-full bg-base-200">
<li><a>Home</a></li>
<li><a>About</a></li>
<li><a>Contact</a></li>
</ul>
</div>
</div>
Styling Notes
- Width: Default
w-80(320px), adjust with Tailwind width utilities - Background: Use DaisyUI color classes like
bg-base-200 - Height: Always use
min-h-fullto ensure full height - Padding: Add
p-4or similar for inner spacing
Desktop Sidebar (Persistent)
Characteristics
- Always visible (no overlay)
- Does not overlay main content
- Main content adjusts to sidebar width
- No toggle button needed
- Used on desktop screens
Implementation with drawer-open
<div class="drawer lg:drawer-open">
<input id="desktop-drawer" type="checkbox" class="drawer-toggle" />
<div class="drawer-content">
<!-- Main content -->
<div class="p-4">
<h1>Main Content</h1>
<p>The sidebar is always visible on desktop (lg and above)</p>
</div>
</div>
<div class="drawer-side">
<!-- No overlay needed for persistent sidebar -->
<label for="desktop-drawer" aria-label="close sidebar" class="drawer-overlay"></label>
<!-- Sidebar menu -->
<ul class="menu p-4 w-80 min-h-full bg-base-200">
<li><a>Dashboard</a></li>
<li><a>Settings</a></li>
<li><a>Profile</a></li>
</ul>
</div>
</div>
How drawer-open Works
The drawer-open class forces the drawer to be permanently open:
<div class="drawer drawer-open">
- Drawer is always visible
- Cannot be toggled closed
drawer-togglecheckbox is ignoreddrawer-overlayis not shown- Main content automatically shifts to accommodate sidebar width
Responsive Usage
Use Tailwind breakpoint modifiers for responsive behavior:
<!-- Open on large screens and above -->
<div class="drawer lg:drawer-open">
<!-- Open on medium screens and above -->
<div class="drawer md:drawer-open">
<!-- Open on extra-large screens and above -->
<div class="drawer xl:drawer-open">
Combined Mobile + Desktop Pattern (Recommended)
This is the most common pattern for responsive applications: mobile overlay + desktop persistent.
Complete Implementation
<div class="drawer lg:drawer-open">
<!-- Checkbox for mobile toggle -->
<input id="app-drawer" type="checkbox" class="drawer-toggle" />
<div class="drawer-content flex flex-col">
<!-- Navbar with mobile menu button -->
<div class="navbar bg-base-100 lg:hidden">
<div class="flex-none">
<label for="app-drawer" class="btn btn-square btn-ghost">
<!-- Hamburger icon -->
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" class="inline-block w-5 h-5 stroke-current">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path>
</svg>
</label>
</div>
<div class="flex-1">
<a class="btn btn-ghost text-xl">My App</a>
</div>
</div>
<!-- Main content -->
<div class="flex-1 p-6">
<h1 class="text-3xl font-bold mb-4">Welcome</h1>
<p>This is the main content area.</p>
<p>On mobile (< lg): sidebar is hidden, hamburger menu visible</p>
<p>On desktop (≥ lg): sidebar is persistent, hamburger menu hidden</p>
</div>
</div>
<div class="drawer-side">
<!-- Overlay only shows on mobile -->
<label for="app-drawer" aria-label="close sidebar" class="drawer-overlay"></label>
<!-- Sidebar navigation -->
<aside class="bg-base-200 w-80 min-h-full">
<!-- Logo/Header area -->
<div class="p-4 font-bold text-xl border-b border-base-300">
My App Logo
</div>
<!-- Navigation menu -->
<ul class="menu p-4">
<li><a>
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<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>
Dashboard
</a></li>
<li><a>
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
Documents
</a></li>
<li><a>
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>
Settings
</a></li>
</ul>
</aside>
</div>
</div>
Behavior Breakdown
On Mobile (< 1024px / < lg)
- Sidebar is hidden by default
- Hamburger button visible in navbar
- Clicking hamburger opens sidebar as overlay
- Clicking overlay or close button closes sidebar
- Sidebar slides in from left with animation
On Desktop (≥ 1024px / ≥ lg)
lg:drawer-openkeeps sidebar permanently visible- Hamburger button hidden via
lg:hidden - Sidebar takes up fixed width (320px)
- Main content area adjusts automatically
- No overlay, no toggle needed
Tailwind Breakpoints Reference
/* Default (mobile-first) */
/* < 640px */
sm: /* ≥ 640px */
md: /* ≥ 768px */
lg: /* ≥ 1024px */ ← Common desktop breakpoint
xl: /* ≥ 1280px */
2xl: /* ≥ 1536px */
Key Classes Summary
| Class | Purpose |
|---|---|
drawer |
Main container |
drawer-toggle |
Hidden checkbox for state control |
drawer-content |
Main content area |
drawer-side |
Sidebar container |
drawer-overlay |
Clickable overlay (closes drawer) |
drawer-open |
Forces drawer to stay open |
drawer-end |
Positions drawer on the right side |
lg:drawer-open |
Opens drawer on large screens only |
Positioning Variants
Left Side Drawer (Default)
<div class="drawer">
<!-- Drawer appears on the left -->
</div>
Right Side Drawer
<div class="drawer drawer-end">
<!-- Drawer appears on the right -->
</div>
Best Practices
1. Accessibility
- Always include
aria-labelon overlay:<label for="drawer" aria-label="close sidebar" class="drawer-overlay"></label> - Use semantic HTML (
<nav>,<aside>) - Ensure keyboard navigation works (native checkbox provides this)
2. Responsive Design
- Use
lg:drawer-openfor desktop persistence - Hide mobile toggle button on desktop:
lg:hidden - Adjust sidebar width for mobile if needed:
w-64 md:w-80
3. Performance
- DaisyUI drawer is pure CSS (no JavaScript needed)
- Animations are handled by CSS transitions
- No performance overhead
4. Styling
- Use DaisyUI theme colors:
bg-base-200,text-base-content - Maintain consistent spacing:
p-4,gap-2 - Use DaisyUI menu component for navigation:
<ul class="menu">
5. Content Structure
<div class="drawer-content flex flex-col">
<!-- Navbar (if needed) -->
<div class="navbar">...</div>
<!-- Main content with flex-1 to fill space -->
<div class="flex-1 p-6">
<!-- Your content -->
</div>
<!-- Footer (if needed) -->
<footer>...</footer>
</div>
Common Patterns
Pattern 1: Drawer with Close Button
<div class="drawer-side">
<label for="drawer" class="drawer-overlay"></label>
<aside class="bg-base-200 w-80 min-h-full relative">
<!-- Close button (mobile only) -->
<label for="drawer" class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2 lg:hidden">✕</label>
<!-- Sidebar content -->
<ul class="menu p-4 pt-12">
<li><a>Item 1</a></li>
</ul>
</aside>
</div>
Pattern 2: Drawer with User Profile
<aside class="bg-base-200 w-80 min-h-full flex flex-col">
<!-- Logo -->
<div class="p-4 font-bold text-xl">My App</div>
<!-- Navigation (flex-1 to push footer down) -->
<ul class="menu flex-1 p-4">
<li><a>Dashboard</a></li>
<li><a>Settings</a></li>
</ul>
<!-- User profile footer -->
<div class="p-4 border-t border-base-300">
<div class="flex items-center gap-2">
<div class="avatar">
<div class="w-10 rounded-full">
<img src="/avatar.jpg" alt="User" />
</div>
</div>
<div>
<div class="font-semibold">John Doe</div>
<div class="text-sm opacity-70">john@example.com</div>
</div>
</div>
</div>
</aside>
Pattern 3: Nested Menu with Submenu
<ul class="menu p-4 w-80 min-h-full bg-base-200">
<li><a>Dashboard</a></li>
<!-- Submenu -->
<li>
<details>
<summary>Products</summary>
<ul>
<li><a>Electronics</a></li>
<li><a>Clothing</a></li>
<li><a>Books</a></li>
</ul>
</details>
</li>
<li><a>Settings</a></li>
</ul>
Troubleshooting
Issue: Drawer doesn't open on mobile
Solution: Check that:
- Checkbox
idmatches labelforattribute - Checkbox has class
drawer-toggle - You're not using
drawer-openon mobile breakpoints
Issue: Drawer overlaps content on desktop
Solution:
- Remove
drawer-openor use responsive variantlg:drawer-open - Ensure you want overlay behavior, not persistent sidebar
Issue: Overlay not clickable
Solution:
- Ensure overlay label has correct
forattribute - Check that overlay is not behind other elements (z-index)
Issue: Content jumps when drawer opens
Solution:
- Add
flex flex-coltodrawer-content - Ensure drawer-side width is fixed (e.g.,
w-80)
Migration from Custom Solutions
If migrating from a custom sidebar implementation:
Replace custom JavaScript
❌ Before:
function toggleDrawer() {
document.getElementById('sidebar').classList.toggle('open');
}
✅ After:
<input id="drawer" type="checkbox" class="drawer-toggle" />
<label for="drawer">Toggle</label>
Replace custom CSS
❌ Before:
.sidebar {
transform: translateX(-100%);
transition: transform 0.3s;
}
.sidebar.open {
transform: translateX(0);
}
✅ After:
<div class="drawer">
<!-- DaisyUI handles all transitions -->
</div>
Replace media query logic
❌ Before:
@media (min-width: 1024px) {
.sidebar { display: block; }
.toggle-button { display: none; }
}
✅ After:
<div class="drawer lg:drawer-open">
<label for="drawer" class="lg:hidden">Toggle</label>
</div>
Summary
The DaisyUI drawer pattern provides:
✅ Zero JavaScript - Pure CSS solution ✅ Accessible - Built-in keyboard support via checkbox ✅ Responsive - Easy mobile/desktop variants with Tailwind ✅ Themeable - Uses DaisyUI theme colors ✅ Flexible - Supports left/right positioning ✅ Standard - No custom CSS needed
Recommended approach: Use lg:drawer-open for desktop with hidden mobile toggle for best responsive experience.