Mobile-First Header — Complete Guide
In this tutorial, you will learn about Mobile. We cover key concepts, practical examples, and best practices to help you master this topic.
Mobile-first headers use compact height, hamburger menus, sticky positioning, search integration, and safe-area insets for navigation on small screens.
What You'll Learn
- Compact header sizing
- Hamburger menu and overlay nav
- Sticky vs static headers
- Search bar integration
- Header with actions (cart, profile)
- Safe area insets for notched devices
- Collapsible header on scroll
Why It Matters
- Headers take valuable screen real estate on mobile
- Oversized headers push content below the fold
- Poor navigation hides important actions
- Scrolling with a sticky header reduces content area
Real-World Use
- An e-commerce header with search and cart
- A news site with collapsible header on scroll
- A dashboard with profile and notifications
- A documentation site with hamburger menu
flowchart LR A[Mobile-First Header] --> B[Sizing] A --> C[Navigation] A --> D[Sticky] A --> E[Actions] B --> F[48-56px height] C --> G[Hamburger menu] D --> H[Sticky + collapse] E --> I[Search + icons]
Compact Header Sizing
Mobile headers should be compact — typically 48-56px — to maximize content area.
Code Example: Basic Compact Header
<header class="mobile-header">
<button class="header-btn" aria-label="Open menu">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M3 12h18M3 6h18M3 18h18"/>
</svg>
</button>
<a href="/" class="header-logo">
<span class="logo-text">DodaTech</span>
</a>
<div class="header-actions">
<button class="header-btn" aria-label="Search">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="11" cy="11" r="8"/>
<path d="M21 21l-4.35-4.35"/>
</svg>
</button>
<button class="header-btn" aria-label="Notifications">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M18 8A6 6 0 006 8c0 7-3 9-3 9h18s-3-2-3-9"/>
</svg>
<span class="header-badge">3</span>
</button>
</div>
</header>
<style>
.mobile-header {
display: flex;
align-items: center;
justify-content: space-between;
height: 56px;
padding: 0 0.75rem;
background: #fff;
border-bottom: 1px solid #e5e7eb;
position: sticky;
top: 0;
z-index: 100;
}
.header-logo {
text-decoration: none;
}
.logo-text {
font-size: 1.25rem;
font-weight: 700;
color: #1f2937;
}
.header-actions {
display: flex;
align-items: center;
gap: 0.25rem;
}
.header-btn {
position: relative;
width: 44px;
height: 44px;
display: flex;
align-items: center;
justify-content: center;
border: none;
background: transparent;
border-radius: 8px;
cursor: pointer;
color: #6b7280;
-webkit-tap-highlight-color: transparent;
touch-action: manipulation;
}
.header-btn:active {
background: #f3f4f6;
color: #374151;
}
.header-badge {
position: absolute;
top: 4px;
right: 4px;
min-width: 18px;
height: 18px;
padding: 0 4px;
font-size: 0.625rem;
font-weight: 700;
color: #fff;
background: #ef4444;
border-radius: 9px;
display: flex;
align-items: center;
justify-content: center;
}
</style>
Expected output: A compact 56px header with hamburger menu on the left, logo in the center, and action icons (search, notifications with badge) on the right. All buttons are 44x44px touch targets.
Hamburger Menu and Overlay Navigation
The hamburger menu is the standard mobile navigation pattern.
Code Example: Hamburger Menu
<header class="mobile-header" id="app-header">
<button class="header-btn hamburger-btn" id="menu-btn" aria-label="Open menu" aria-expanded="false">
<span class="hamburger-line"></span>
<span class="hamburger-line"></span>
<span class="hamburger-line"></span>
</button>
<a href="/" class="header-logo"><span class="logo-text">DodaTech</span></a>
<div class="header-actions">
<button class="header-btn" aria-label="Search">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="11" cy="11" r="8"/><path d="M21 21l-4.35-4.35"/>
</svg>
</button>
</div>
</header>
<nav class="mobile-nav-overlay" id="nav-overlay" hidden>
<div class="nav-overlay-backdrop"></div>
<div class="nav-drawer" role="dialog" aria-modal="true" aria-label="Navigation menu">
<div class="nav-drawer-header">
<span class="logo-text">DodaTech</span>
<button class="header-btn" id="close-menu" aria-label="Close menu">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M18 6L6 18M6 6l12 12"/>
</svg>
</button>
</div>
<ul class="nav-drawer-list">
<li><a href="#" class="nav-drawer-link active">Home</a></li>
<li><a href="#" class="nav-drawer-link">Tutorials</a></li>
<li><a href="#" class="nav-drawer-link">Courses</a></li>
<li><a href="#" class="nav-drawer-link">Blog</a></li>
<li><a href="#" class="nav-drawer-link">About</a></li>
<li><a href="#" class="nav-drawer-link">Contact</a></li>
</ul>
<div class="nav-drawer-footer">
<a href="#" class="nav-drawer-link">Log In</a>
<a href="#" class="nav-drawer-link nav-signup">Sign Up</a>
</div>
</div>
</nav>
<script>
const menuBtn = document.getElementById('menu-btn');
const closeMenu = document.getElementById('close-menu');
const overlay = document.getElementById('nav-overlay');
const backdrop = overlay.querySelector('.nav-overlay-backdrop');
function openNav() {
overlay.hidden = false;
document.body.style.overflow = 'hidden';
menuBtn.setAttribute('aria-expanded', 'true');
requestAnimationFrame(() => {
overlay.querySelector('.nav-drawer').classList.add('open');
backdrop.classList.add('open');
});
}
function closeNav() {
overlay.querySelector('.nav-drawer').classList.remove('open');
backdrop.classList.remove('open');
menuBtn.setAttribute('aria-expanded', 'false');
overlay.addEventListener('transitionend', function handler() {
overlay.hidden = true;
overlay.removeEventListener('transitionend', handler);
document.body.style.overflow = '';
}, { once: true });
}
menuBtn.addEventListener('click', openNav);
closeMenu.addEventListener('click', closeNav);
backdrop.addEventListener('click', closeNav);
</script>
<style>
.hamburger-btn {
display: flex;
flex-direction: column;
gap: 5px;
padding: 10px;
}
.hamburger-line {
display: block;
width: 24px;
height: 2px;
background: #1f2937;
border-radius: 1px;
transition: transform 0.2s;
}
.mobile-nav-overlay {
position: fixed;
inset: 0;
z-index: 200;
}
.nav-overlay-backdrop {
position: absolute;
inset: 0;
background: rgba(0, 0, 0, 0.5);
opacity: 0;
transition: opacity 0.3s ease;
}
.nav-overlay-backdrop.open {
opacity: 1;
}
.nav-drawer {
position: absolute;
top: 0;
left: 0;
bottom: 0;
width: 300px;
max-width: 85vw;
background: #fff;
transform: translateX(-100%);
transition: transform 0.3s cubic-bezier(0.32, 0.72, 0, 1);
display: flex;
flex-direction: column;
padding-top: env(safe-area-inset-top, 0px);
}
.nav-drawer.open {
transform: translateX(0);
}
.nav-drawer-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.75rem 1rem;
border-bottom: 1px solid #e5e7eb;
min-height: 56px;
}
.nav-drawer-list {
list-style: none;
padding: 0.5rem 0;
margin: 0;
flex: 1;
overflow-y: auto;
}
.nav-drawer-link {
display: block;
padding: 0.875rem 1rem;
font-size: 1rem;
font-weight: 500;
color: #374151;
text-decoration: none;
min-height: 44px;
line-height: 1.3;
-webkit-tap-highlight-color: transparent;
}
.nav-drawer-link:active {
background: #f3f4f6;
}
.nav-drawer-link.active {
color: #3b82f6;
font-weight: 600;
}
.nav-drawer-footer {
border-top: 1px solid #e5e7eb;
padding: 0.5rem 0;
}
.nav-signup {
color: #3b82f6;
font-weight: 600;
}
</style>
Expected output: Tapping the hamburger icon slides a navigation drawer from the left with a backdrop overlay. The drawer has a header with close button, navigation links, and a footer with login/signup. The backdrop is tappable to close.
Collapsible Header on Scroll
Hide the header when scrolling down and reveal it when scrolling up to maximize content space.
Code Example: Collapsible Header
<header class="collapsible-header" id="collapsible-header">
<button class="header-btn" aria-label="Open menu">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M3 12h18M3 6h18M3 18h18"/>
</svg>
</button>
<a href="/" class="header-logo"><span class="logo-text">DodaTech</span></a>
<div class="header-actions">
<button class="header-btn" aria-label="Search">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="11" cy="11" r="8"/><path d="M21 21l-4.35-4.35"/>
</svg>
</button>
</div>
</header>
<main class="content" style="padding: 1rem; max-width: 480px;">
<p style="margin-bottom: 1rem; color: #6b7280;">Scroll down to see the header collapse. Scroll up to reveal it again.</p>
<div style="height: 2000px; background: linear-gradient(180deg, #f9fafb, #e5e7eb); border-radius: 8px; display: flex; align-items: center; justify-content: center; color: #9ca3af;">Content area</div>
</main>
<script>
const header = document.getElementById('collapsible-header');
let lastScrollY = 0;
let ticking = false;
window.addEventListener('scroll', () => {
if (!ticking) {
requestAnimationFrame(() => {
const currentScrollY = window.scrollY;
if (currentScrollY > 80) {
if (currentScrollY > lastScrollY) {
header.style.transform = 'translateY(-100%)';
} else {
header.style.transform = 'translateY(0)';
}
} else {
header.style.transform = 'translateY(0)';
}
lastScrollY = currentScrollY;
ticking = false;
});
ticking = true;
}
}, { passive: true });
</script>
<style>
.collapsible-header {
position: fixed;
top: 0;
left: 0;
right: 0;
display: flex;
align-items: center;
justify-content: space-between;
height: 56px;
padding: 0 0.75rem;
background: #fff;
border-bottom: 1px solid #e5e7eb;
z-index: 100;
transition: transform 0.3s ease;
}
.content {
margin-top: 56px;
}
</style>
Expected output: When scrolling down past 80px, the header slides up and hides. When scrolling up, the header slides back into view. This maximizes content space while keeping navigation accessible.
Header with Search Bar
Integrating search into the header for quick access.
Code Example: Header with Search
<header class="search-header" id="search-header">
<div class="search-header-row">
<button class="header-btn" aria-label="Back">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M19 12H5M12 19l-7-7 7-7"/>
</svg>
</button>
<div class="search-box">
<svg class="search-box-icon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="11" cy="11" r="8"/><path d="M21 21l-4.35-4.35"/>
</svg>
<input
type="search"
class="search-box-input"
placeholder="Search tutorials..."
autocomplete="off"
inputmode="search"
enterkeyhint="search"
>
<button class="search-clear" aria-label="Clear search" hidden>
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M18 6L6 18M6 6l12 12"/>
</svg>
</button>
</div>
<button class="header-btn" aria-label="Cancel">Cancel</button>
</div>
<div class="search-suggestions" id="search-suggestions" hidden>
<div class="suggestions-group">
<h4 class="suggestions-label">Recent Searches</h4>
<ul class="suggestions-list">
<li><button class="suggestion-item">CSS Flexbox</button></li>
<li><button class="suggestion-item">JavaScript promises</button></li>
<li><button class="suggestion-item">Responsive design</button></li>
</ul>
</div>
<div class="suggestions-group">
<h4 class="suggestions-label">Popular</h4>
<ul class="suggestions-list">
<li><button class="suggestion-item">Mobile-first forms</button></li>
<li><button class="suggestion-item">CSS Grid complete guide</button></li>
</ul>
</div>
</div>
</header>
<script>
const searchInput = document.querySelector('.search-box-input');
const searchSuggestions = document.getElementById('search-suggestions');
const clearBtn = document.querySelector('.search-clear');
searchInput.addEventListener('focus', () => {
searchSuggestions.hidden = false;
});
searchInput.addEventListener('input', function() {
clearBtn.hidden = this.value.length === 0;
});
clearBtn.addEventListener('click', () => {
searchInput.value = '';
searchInput.focus();
clearBtn.hidden = true;
});
document.addEventListener('click', (e) => {
if (!e.target.closest('.search-header')) {
searchSuggestions.hidden = true;
}
});
</script>
<style>
.search-header {
background: #fff;
border-bottom: 1px solid #e5e7eb;
position: sticky;
top: 0;
z-index: 100;
}
.search-header-row {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 0.75rem;
}
.search-box {
flex: 1;
position: relative;
display: flex;
align-items: center;
}
.search-box-icon {
position: absolute;
left: 0.75rem;
color: #9ca3af;
pointer-events: none;
}
.search-box-input {
width: 100%;
height: 40px;
padding: 0 2.5rem 0 2.5rem;
font-size: 0.9375rem;
border: 2px solid #e5e7eb;
border-radius: 8px;
background: #f9fafb;
font-family: inherit;
transition: border-color 0.2s;
-webkit-appearance: none;
appearance: none;
}
.search-box-input:focus {
border-color: #3b82f6;
outline: none;
background: #fff;
}
.search-clear {
position: absolute;
right: 0.25rem;
width: 36px;
height: 36px;
display: flex;
align-items: center;
justify-content: center;
border: none;
background: transparent;
border-radius: 50%;
cursor: pointer;
color: #9ca3af;
}
.search-suggestions {
padding: 0.5rem 1rem 1rem;
border-top: 1px solid #e5e7eb;
}
.suggestions-group {
margin-bottom: 1rem;
}
.suggestions-label {
font-size: 0.75rem;
font-weight: 600;
color: #9ca3af;
text-transform: uppercase;
letter-spacing: 0.05em;
margin: 0 0 0.5rem;
}
.suggestions-list {
list-style: none;
padding: 0;
margin: 0;
}
.suggestion-item {
width: 100%;
text-align: left;
padding: 0.625rem 0;
font-size: 0.875rem;
color: #374151;
background: transparent;
border: none;
cursor: pointer;
min-height: 44px;
-webkit-tap-highlight-color: transparent;
}
.suggestion-item:active {
color: #3b82f6;
}
</style>
Expected output: The header has a centered search input with back button and cancel action. On focus, recent searches and popular suggestions appear below the search bar. The clear button appears when text is entered.
Common Mistakes
- Oversized header (60px+) on mobile — Every extra pixel of header height pushes content below the fold. Keep it 48-56px.
- Sticky header without collapse — A permanently visible sticky header reduces content height by 56px on every screen.
- Hamburger too small — The hamburger icon itself must be inside a 44x44px touch target, not just the icon size.
- No active state on nav links — Users cannot tell which page they are on. Use a highlighted active state.
- Header without safe area padding — On notched devices, content hides behind the notch or status bar.
- Search bar that covers the header — A full-width search bar that replaces the header loses navigation. Use an overlay or push layout.
- No backdrop close on drawer — Users must find a close button instead of tapping the overlay to dismiss the menu.
Practice Questions
- What is the ideal height for a mobile header? 48-56px. This provides enough space for touch targets without wasting screen real estate.
- How does a collapsible header improve UX? It hides on scroll down to maximize content area and reveals on scroll up to provide navigation access.
- What is the purpose of the hamburger menu? It hides navigation links behind a button, saving header space while providing access to the full navigation.
- What CSS property prevents header overlap on notched devices? padding-top: env(safe-area-inset-top, 0px) adds padding for the status bar area.
- How do you ensure hamburger menu Accessibility? Use aria-label on the button, aria-expanded for menu state, and role="dialog" with aria-modal="true" on the drawer.
Challenge
Build a complete header system for a documentation site. Include: (1) a sticky 56px header with hamburger menu, logo, and search icon, (2) a collapsible behavior that hides on scroll down and reveals on scroll up, (3) a left drawer navigation with 3 sections (Getting Started, API Reference, Guides) each with 4-5 links, (4) active state highlighting, (5) search overlay that pushes content down, (6) safe area insets, (7) backdrop close for the drawer, (8) focus trap when the drawer is open, (9) Escape key to close the drawer.
FAQ
Mini Project
Build a responsive header for an e-commerce site. On mobile: (1) 56px sticky header with hamburger, logo, search icon, and cart icon with badge, (2) collapsible on scroll down, (3) left drawer with 15 links organized in 4 categories (Men, Women, Kids, Sale) with a footer for Account and Help, (4) search overlay that shows recent searches and popular products, (5) cart preview dropdown showing 3 items with total and checkout button. On desktop: show horizontal nav links, hide the hamburger, keep search and cart icons.
What's Next
Continue with Lesson 15: Mobile-First Performance to optimize web performance for mobile devices.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro