Why mobile navigation is different
Over 60% of web traffic is mobile. You can't just shrink a desktop menu — screens are narrow, fingers are wide, and users expect specific patterns. Great mobile nav is invisible until you need it.
Screen real estate
A phone screen is ~5× smaller than a laptop. Every pixel a nav steals is a pixel stolen from the content.
Thumb zones
Users hold phones one-handed. Navigation needs to be reachable with a thumb — edges and bottom, not far corners.
Speed matters
Mobile users are in motion. One-second delays cost you visitors. Nav has to open instantly, with no jank.
Expected patterns
Users scan for a hamburger icon, top-right. Don't reinvent the pattern — make the familiar one feel great.
Live demo — slide from the right
Tap the hamburger icon in the phone mockup to see a right-side drawer slide in. Notice the overlay dim effect, the smooth 350ms transition, and how tapping outside closes it.
Welcome back
Your dashboard is ready. Tap the menu to navigate between sections.
The code — HTML, CSS, JS
Three pieces: the hamburger button, the drawer markup, and a tiny bit of JS to toggle a class. The animation is pure CSS. The backdrop is a separate element with its own click handler.
<!-- Top bar with brand + hamburger trigger --> <header class="topbar"> <span class="brand">Acme</span> <button class="hamburger" id="menuToggle" aria-label="Open menu"> <i class="ph-bold ph-list"></i> </button> </header> <!-- Dim overlay — clicking it closes the drawer --> <div class="drawer-overlay" id="drawerOverlay"></div> <!-- The drawer itself — slides in from the RIGHT --> <nav class="drawer" id="drawer" aria-label="Main navigation"> <div class="drawer-head"> <strong>Menu</strong> <button class="drawer-close" id="menuClose"> <i class="ph-bold ph-x"></i> </button> </div> <a href="#"><i class="ph-fill ph-house"></i> Home</a> <a href="#"><i class="ph-bold ph-compass"></i> Explore</a> <a href="#"><i class="ph-bold ph-chat-circle"></i> Messages</a> <a href="#"><i class="ph-bold ph-gear"></i> Settings</a> </nav>
/* Top bar */ .topbar { display: flex; justify-content: space-between; align-items: center; padding: 14px 16px; background: #0f172a; } .brand { font-weight: 800; color: #fff; } /* Hamburger — a simple icon button */ .hamburger { background: none; border: none; color: #fff; font-size: 1.6rem; padding: 8px; cursor: pointer; border-radius: 8px; } .hamburger:hover { background: rgba(255,255,255,.1); } /* Dim backdrop — invisible until .open is added to body */ .drawer-overlay { position: fixed; inset: 0; background: rgba(0,0,0,.5); opacity: 0; pointer-events: none; transition: opacity .3s; z-index: 10; } body.menu-open .drawer-overlay { opacity: 1; pointer-events: auto; } /* THE DRAWER — positioned on the RIGHT, off-screen by default */ .drawer { position: fixed; top: 0; right: 0; /* ← anchored to the right edge */ bottom: 0; width: min(320px, 80vw); background: #0f172a; box-shadow: -10px 0 30px rgba(0,0,0,.4); transform: translateX(100%); /* hidden off-screen right */ transition: transform .35s cubic-bezier(.4,0,.2,1); z-index: 11; overflow-y: auto; } /* Slide it IN when body has .menu-open */ body.menu-open .drawer { transform: translateX(0); } /* Drawer internals */ .drawer-head { display: flex; justify-content: space-between; align-items: center; padding: 18px; border-bottom: 1px solid rgba(255,255,255,.08); } .drawer a { display: flex; align-items: center; gap: 12px; padding: 14px 18px; color: #cbd5e1; text-decoration: none; font-weight: 500; } .drawer a:hover { background: rgba(59,130,246,.1); color: #fff; } .drawer-close { background: none; border: none; color: #fff; font-size: 1.4rem; cursor: pointer; }
// Grab the three interactive elements const toggle = document.getElementById('menuToggle'); const closeBtn = document.getElementById('menuClose'); const overlay = document.getElementById('drawerOverlay'); // Add / remove the .menu-open class on <body> // That class triggers the CSS transitions for drawer + overlay function openMenu() { document.body.classList.add('menu-open'); } function closeMenu() { document.body.classList.remove('menu-open'); } toggle.addEventListener('click', openMenu); closeBtn.addEventListener('click', closeMenu); overlay.addEventListener('click', closeMenu); // Bonus: close on Escape key (accessibility) document.addEventListener('keydown', e => { if (e.key === 'Escape') closeMenu(); });
Mobile nav rules
Small details separate a mobile menu that feels native from one that feels clunky. Here's what to get right — and what to avoid.
Do
- Place the hamburger in the top corner (left or right)
- Make tap targets at least 44×44px
- Use
transformfor smooth 60fps animation - Dim the background with an overlay
- Let users tap outside or press Esc to close
- Add
aria-labelto icon-only buttons - Lock body scroll while the drawer is open
- Highlight the current page in the menu
Don't
- Animate
leftorwidth— usetransformonly - Use tiny 16px tap targets — thumbs can't hit them
- Hide critical actions (login, cart) in the menu only
- Nest 3+ levels of sub-menus — users get lost
- Use a 2-second transition — 250–350ms is ideal
- Leave the body scrollable behind the drawer
- Skip the close button — some users won't find the tap-outside trick
- Use the same nav for phone and desktop without testing both
Other mobile nav patterns
The right-side drawer isn't the only option. Here are the four most common mobile nav patterns — and when to use each.
Drawer (off-canvas)
Slides in from the side. Great for apps with 5+ nav items. What we just built.
Best for appsBottom tab bar
Fixed tabs at the bottom — always visible, thumb-friendly, iOS/Android native.
Best for 3–5 itemsFull-screen overlay
Menu takes over the whole screen when opened. Dramatic, focused, marketing-friendly.
Best for landing pagesDropdown / accordion
Menu expands down from the top bar. Simple, but can push content awkwardly.
Best for docs / small sitesMinimal right-slide drawer
The whole pattern condensed. Copy this into any project and you have a working mobile menu in under 30 lines of CSS.
.drawer { position: fixed; top: 0; right: 0; bottom: 0; width: min(320px, 80vw); background: #fff; transform: translateX(100%); transition: transform .3s; z-index: 100; } body.menu-open .drawer { transform: translateX(0); } .drawer-overlay { position: fixed; inset: 0; background: rgba(0,0,0,.5); opacity: 0; pointer-events: none; transition: opacity .3s; z-index: 99; } body.menu-open .drawer-overlay { opacity: 1; pointer-events: auto; }
const open = () => document.body.classList.add('menu-open'); const close = () => document.body.classList.remove('menu-open'); menuToggle.addEventListener('click', open); drawerOverlay.addEventListener('click', close); menuClose.addEventListener('click', close); document.addEventListener('keydown', e => e.key === 'Escape' && close());