Toggle navigation
☰
Home
HTML
CSS
Scripting
Database
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Off-Canvas Slide-in Navbar</title> <style> /* ======================================== CSS Custom Properties (App Theme) ======================================== */ :root { --color-background: #f4f7f6; --color-surface: #ffffff; --color-text-primary: #1d1d1f; --color-text-secondary: #5f5f65; --color-primary: #0066cc; --color-overlay: rgba(0, 0, 0, 0.5); --offcanvas-width: 280px; --font-family-sans-serif: "SF Pro Text", "Helvetica Neue", -apple-system, sans-serif; --navbar-height: 70px; --transition-speed: 0.35s; --transition-curve: cubic-bezier(0.7, 0, 0.3, 1); } /* ======================================== Base & Layout Styles ======================================== */ *, *::before, *::after { box-sizing: border-box; } html, body { height: 100%; margin: 0; } body { font-family: var(--font-family-sans-serif); background-color: var(--color-background); color: var(--color-text-primary); overflow-x: hidden; /* Prevent horizontal scrollbars */ } /* The main page wrapper that gets "pushed" All primary content goes inside this wrapper. */ .page-wrapper { position: relative; /* Stacking context */ min-height: 100%; transition: transform var(--transition-speed) var(--transition-curve); background-color: var(--color-background); } .main-header { display: flex; align-items: center; height: var(--navbar-height); padding: 0 1.5rem; background-color: var(--color-surface); box-shadow: 0 1px 3px rgba(0,0,0,0.1); } .main-content { padding: 1.5rem; } /* ======================================== Off-Canvas Navigation Styles ======================================== */ .offcanvas-nav { position: fixed; top: 0; left: 0; z-index: 2000; width: var(--offcanvas-width); height: 100%; padding: 1.5rem; background-color: var(--color-surface); transform: translateX(-100%); /* Start hidden off-screen */ transition: transform var(--transition-speed) var(--transition-curve); will-change: transform; } .offcanvas-nav__header { display: flex; justify-content: flex-end; align-items: center; margin-bottom: 2rem; } .offcanvas-nav__link { display: block; padding: 0.75rem 0; font-size: 1.1rem; color: var(--color-text-secondary); font-weight: 500; } .offcanvas-nav__link:hover { color: var(--color-primary); } /* ======================================== Buttons: Open & Close ======================================== */ .icon-button { display: flex; align-items: center; justify-content: center; border: none; background: transparent; padding: 0.5rem; cursor: pointer; border-radius: 50%; } .icon-button svg { width: 28px; height: 28px; } .icon-button:hover { background-color: rgba(0,0,0,0.05); } /* ======================================== Overlay Style ======================================== */ .offcanvas-overlay { position: fixed; top: 0; left: 0; z-index: 1500; width: 100vw; height: 100vh; background-color: var(--color-overlay); opacity: 0; visibility: hidden; /* Hidden by default */ transition: opacity var(--transition-speed) ease, visibility var(--transition-speed) ease; } /* ======================================== The Active State (Controlled by JS) ======================================== A single class on the <body> controls everything. */ body.offcanvas-is-active .offcanvas-nav { transform: translateX(0); /* Slide in */ } body.offcanvas-is-active .offcanvas-overlay { opacity: 1; visibility: visible; /* Show overlay */ } body.offcanvas-is-active .page-wrapper { transform: translateX(var(--offcanvas-width)); /* Push content */ border-radius: 12px; overflow: hidden; } </style> </head> <body> <!-- This overlay darkens the main content when the menu is open --> <div class="offcanvas-overlay" id="offcanvas-overlay"></div> <!-- The navigation menu itself, hidden by default --> <nav class="offcanvas-nav" id="offcanvas-nav" aria-hidden="true"> <div class="offcanvas-nav__header"> <button class="icon-button" id="offcanvas-close-btn" aria-label="Close menu"> <svg viewBox="0 0 24 24"><path fill="currentColor" d="M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z" /></svg> </button> </div> <ul class="offcanvas-nav__list"> <li class="offcanvas-nav__item"><a href="#" class="offcanvas-nav__link">Home</a></li> <li class="offcanvas-nav__item"><a href="#" class="offcanvas-nav__link">Dashboard</a></li> <li class="offcanvas-nav__item"><a href="#" class="offcanvas-nav__link">Analytics</a></li> <li class="offcanvas-nav__item"><a href="#" class="offcanvas-nav__link">Settings</a></li> <li class="offcanvas-nav__item"><a href="#" class="offcanvas-nav__link">Profile</a></li> </ul> </nav> <!-- This wrapper contains the visible page content --> <div class="page-wrapper"> <header class="main-header"> <button class="icon-button" id="offcanvas-open-btn" aria-label="Open menu" aria-haspopup="true" aria-controls="offcanvas-nav"> <svg viewBox="0 0 24 24"><path fill="currentColor" d="M3,6H21V8H3V6M3,11H21V13H3V11M3,16H21V18H3V16Z" /></svg> </button> <h1 style="font-size: 1.25rem; margin-left: 1rem;">Dashboard</h1> </header> <main class="main-content"> <p>Page content here. When you open the menu, this whole area will be pushed to the right.</p> </main> </div> <script> document.addEventListener('DOMContentLoaded', function() { const body = document.body; const openBtn = document.getElementById('offcanvas-open-btn'); const closeBtn = document.getElementById('offcanvas-close-btn'); const overlay = document.getElementById('offcanvas-overlay'); const nav = document.getElementById('offcanvas-nav'); if (!openBtn || !closeBtn || !overlay || !nav) return; let lastActiveElement; // To store the element that opened the menu function openMenu() { lastActiveElement = document.activeElement; // Save focus body.classList.add('offcanvas-is-active'); nav.setAttribute('aria-hidden', 'false'); openBtn.setAttribute('aria-expanded', 'true'); // Move focus to the close button when menu opens closeBtn.focus(); } function closeMenu() { body.classList.remove('offcanvas-is-active'); nav.setAttribute('aria-hidden', 'true'); openBtn.setAttribute('aria-expanded', 'false'); // Return focus to the element that opened the menu if (lastActiveElement) { lastActiveElement.focus(); } } // --- Event Listeners --- openBtn.addEventListener('click', openMenu); closeBtn.addEventListener('click', closeMenu); overlay.addEventListener('click', closeMenu); // Close menu with Escape key document.addEventListener('keydown', (e) => { if (e.key === 'Escape' && body.classList.contains('offcanvas-is-active')) { closeMenu(); } }); // --- Accessible Focus Trap --- // This is crucial for accessibility compliance (WCAG) nav.addEventListener('keydown', (e) => { if (e.key === 'Tab' && body.classList.contains('offcanvas-is-active')) { const focusableElements = nav.querySelectorAll('a[href], button, input, textarea, select, details, [tabindex]:not([tabindex="-1"])'); const firstElement = focusableElements[0]; const lastElement = focusableElements[focusableElements.length - 1]; if (e.shiftKey) { // Shift + Tab if (document.activeElement === firstElement) { lastElement.focus(); e.preventDefault(); } } else { // Tab if (document.activeElement === lastElement) { firstElement.focus(); e.preventDefault(); } } } }); }); </script> </body> </html>