Responsive Navigation Patterns
Why Navigation Patterns Matter for Responsive Design
Navigation is the most critical user interface element on any website. It is how users discover content, move between sections, and orient themselves within your site's information architecture. On a large desktop screen with ample horizontal space, navigation is straightforward -- you can display all links in a horizontal bar. But on a 320-pixel-wide phone screen, that same horizontal bar would either overflow, wrap uncontrollably, or render links too small to tap. Responsive navigation patterns solve this problem by adapting the navigation's layout, visibility, and interaction model to the available screen size.
Choosing the right navigation pattern is not just a visual decision -- it directly impacts usability, accessibility, and conversion rates. A navigation that is difficult to use on mobile can cause visitors to leave your site. Studies consistently show that mobile users expect to find navigation within the first few seconds of landing on a page. This lesson covers the most common and effective responsive navigation patterns, from the ubiquitous hamburger menu to advanced patterns like priority+ navigation and mega menu adaptations.
The Hamburger Menu: The Mobile Standard
The hamburger menu (named for its three horizontal lines that resemble a hamburger) has become the de facto standard for mobile navigation. When the screen is too narrow for a full navigation bar, the links are hidden behind a toggle button. Tapping or clicking the button reveals the navigation, typically as a vertical list that slides in from the side or expands downward from the header.
The hamburger menu's popularity comes from its space efficiency -- it collapses potentially dozens of navigation links into a single icon, freeing up precious screen real estate for content. However, it has valid criticisms: it hides navigation behind an extra tap, which can reduce discoverability. Despite this, it remains the most widely recognized and expected pattern on mobile devices.
Basic Hamburger Menu HTML Structure
<header class="site-header">
<a href="/" class="logo">MySite</a>
<button class="nav-toggle" aria-expanded="false" aria-controls="main-nav">
<span class="hamburger-line"></span>
<span class="hamburger-line"></span>
<span class="hamburger-line"></span>
<span class="sr-only">Menu</span>
</button>
<nav id="main-nav" class="main-nav" aria-label="Main navigation">
<ul class="nav-list">
<li><a href="/home">Home</a></li>
<li><a href="/about">About</a></li>
<li><a href="/services">Services</a></li>
<li><a href="/portfolio">Portfolio</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
</nav>
</header>
Hamburger Menu CSS with Transitions
.site-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 1rem;
position: relative;
}
.nav-toggle {
display: none; /* Hidden on desktop */
background: none;
border: none;
cursor: pointer;
padding: 0.5rem;
z-index: 1001;
}
.hamburger-line {
display: block;
width: 25px;
height: 3px;
margin: 5px 0;
background-color: var(--text-dark);
transition: transform 0.3s ease, opacity 0.3s ease;
}
.nav-list {
display: flex;
gap: 2rem;
list-style: none;
margin: 0;
padding: 0;
}
.nav-list a {
text-decoration: none;
color: var(--text-dark);
padding: 0.5rem;
}
/* Mobile styles */
@media (max-width: 768px) {
.nav-toggle {
display: block;
}
.main-nav {
position: fixed;
top: 0;
right: -100%;
width: 280px;
height: 100vh;
background: var(--bg-white);
box-shadow: -2px 0 10px rgba(0, 0, 0, 0.1);
transition: right 0.3s ease;
z-index: 1000;
padding-top: 5rem;
}
.main-nav.is-open {
right: 0;
}
.nav-list {
flex-direction: column;
gap: 0;
}
.nav-list a {
display: block;
padding: 1rem 1.5rem;
border-bottom: 1px solid var(--border-light);
}
/* Animate hamburger to X */
.nav-toggle.is-active .hamburger-line:nth-child(1) {
transform: translateY(8px) rotate(45deg);
}
.nav-toggle.is-active .hamburger-line:nth-child(2) {
opacity: 0;
}
.nav-toggle.is-active .hamburger-line:nth-child(3) {
transform: translateY(-8px) rotate(-45deg);
}
}
CSS-Only Hamburger Menu: The Checkbox Hack
One of the most elegant CSS techniques is the checkbox hack -- a way to create a toggling hamburger menu without any JavaScript. The technique uses a hidden checkbox input and the CSS :checked pseudo-class to toggle the visibility of the navigation. When the user clicks the label (which is styled as the hamburger icon), it checks or unchecks the hidden checkbox, and adjacent sibling selectors or general sibling selectors apply the open or closed styles.
This approach is useful for simple navigation menus where you want to avoid JavaScript dependencies entirely. It works in all modern browsers and provides a functional mobile menu with pure CSS. However, it has limitations in terms of accessibility -- you need to carefully manage ARIA attributes and focus states, which typically requires a small amount of JavaScript anyway.
CSS-Only Hamburger with Checkbox Hack
/* HTML Structure:
<input type="checkbox" id="nav-toggle" class="nav-checkbox" />
<label for="nav-toggle" class="nav-toggle-label">
<span></span>
</label>
<nav class="main-nav">...</nav>
*/
.nav-checkbox {
display: none; /* Hide the actual checkbox */
}
.nav-toggle-label {
display: none; /* Hidden on desktop */
cursor: pointer;
padding: 0.5rem;
position: relative;
z-index: 1001;
}
.nav-toggle-label span,
.nav-toggle-label span::before,
.nav-toggle-label span::after {
display: block;
width: 25px;
height: 3px;
background: var(--text-dark);
position: relative;
transition: all 0.3s ease;
}
.nav-toggle-label span::before,
.nav-toggle-label span::after {
content: "";
position: absolute;
}
.nav-toggle-label span::before {
top: -8px;
}
.nav-toggle-label span::after {
top: 8px;
}
@media (max-width: 768px) {
.nav-toggle-label {
display: block;
}
.main-nav {
position: fixed;
top: 0;
left: -100%;
width: 280px;
height: 100vh;
background: var(--bg-white);
transition: left 0.3s ease;
padding-top: 5rem;
z-index: 1000;
}
/* When checkbox is checked, show the nav */
.nav-checkbox:checked ~ .main-nav {
left: 0;
}
/* Animate hamburger to X when checked */
.nav-checkbox:checked ~ .nav-toggle-label span {
background: transparent;
}
.nav-checkbox:checked ~ .nav-toggle-label span::before {
transform: rotate(45deg);
top: 0;
}
.nav-checkbox:checked ~ .nav-toggle-label span::after {
transform: rotate(-45deg);
top: 0;
}
}
aria-expanded. For production websites, consider using a small JavaScript enhancement to update ARIA attributes when the checkbox state changes. A pure CSS menu is fine for learning and prototyping, but professional sites should include proper ARIA management.Off-Canvas Navigation
Off-canvas navigation is a pattern where the entire navigation panel lives outside the visible viewport and slides into view when triggered. Unlike a simple dropdown that pushes content down, off-canvas navigation slides in from the left or right side, typically overlaying the content or pushing the entire page content to the side. This pattern is especially common in mobile apps and app-like web experiences.
The off-canvas pattern provides a spacious area for navigation that can accommodate complex menu structures, user profiles, search bars, and other elements that would not fit in a simple dropdown. It creates a clear visual separation between navigation and content.
Off-Canvas Navigation with Page Push Effect
.page-wrapper {
transition: transform 0.3s ease;
min-height: 100vh;
}
.off-canvas-nav {
position: fixed;
top: 0;
left: 0;
width: 280px;
height: 100vh;
background: var(--bg-white);
transform: translateX(-100%);
transition: transform 0.3s ease;
z-index: 1000;
overflow-y: auto;
padding: 2rem 0;
}
/* Overlay behind the nav */
.nav-overlay {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.5);
opacity: 0;
visibility: hidden;
transition: opacity 0.3s ease, visibility 0.3s ease;
z-index: 999;
}
/* When nav is open */
.off-canvas-nav.is-open {
transform: translateX(0);
}
.off-canvas-nav.is-open ~ .nav-overlay {
opacity: 1;
visibility: visible;
}
/* Optional: push the page content */
.off-canvas-nav.is-open ~ .page-wrapper {
transform: translateX(280px);
}
/* Navigation items inside off-canvas */
.off-canvas-nav .nav-section {
padding: 1rem 1.5rem;
border-bottom: 1px solid var(--border-light);
}
.off-canvas-nav .nav-section-title {
font-size: 0.75rem;
text-transform: uppercase;
letter-spacing: 0.1em;
color: var(--text-light);
margin-bottom: 0.75rem;
}
.off-canvas-nav .nav-link {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.75rem 0;
color: var(--text-dark);
text-decoration: none;
}
.off-canvas-nav .nav-link:hover,
.off-canvas-nav .nav-link:focus {
color: var(--primary);
}
Priority+ Navigation Pattern
The priority+ navigation pattern (also called the "more" pattern) is an intelligent approach that shows as many navigation items as will fit in the available space and collapses the remaining items into a "More" dropdown. As the viewport shrinks, items progressively move from the visible bar into the overflow menu. As it grows, items return to the main bar. This pattern is particularly effective because it maximizes visible navigation items at every screen width, unlike the hamburger menu which hides everything at once.
This pattern requires JavaScript to measure available space and determine which items overflow, but the CSS foundation handles the visual presentation. It is commonly seen on news websites and web applications with many navigation sections.
Priority+ Navigation CSS Foundation
.priority-nav {
display: flex;
align-items: center;
overflow: hidden; /* Hide items that overflow */
position: relative;
}
.priority-nav-list {
display: flex;
list-style: none;
margin: 0;
padding: 0;
gap: 0;
flex-wrap: nowrap;
}
.priority-nav-list li {
flex-shrink: 0; /* Prevent items from shrinking */
}
.priority-nav-list a {
display: block;
padding: 1rem 1.25rem;
white-space: nowrap;
text-decoration: none;
color: var(--text-dark);
}
.priority-nav-list a:hover {
background: var(--bg-light);
}
/* The "More" button and its dropdown */
.more-button {
flex-shrink: 0;
padding: 1rem 1.25rem;
background: none;
border: none;
cursor: pointer;
font-weight: 600;
display: none; /* Hidden until JavaScript activates it */
}
.more-button.is-visible {
display: block;
}
.more-dropdown {
position: absolute;
top: 100%;
right: 0;
background: var(--bg-white);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
border-radius: 4px;
min-width: 200px;
opacity: 0;
visibility: hidden;
transform: translateY(-10px);
transition: opacity 0.2s ease, transform 0.2s ease, visibility 0.2s ease;
z-index: 100;
}
.more-dropdown.is-open {
opacity: 1;
visibility: visible;
transform: translateY(0);
}
.more-dropdown a {
display: block;
padding: 0.75rem 1.25rem;
text-decoration: none;
color: var(--text-dark);
border-bottom: 1px solid var(--border-light);
}
.more-dropdown a:last-child {
border-bottom: none;
}
.more-dropdown a:hover {
background: var(--bg-light);
}
Bottom Navigation for Mobile Apps
Bottom navigation bars (tab bars) are a pattern borrowed from native mobile apps that place the primary navigation at the bottom of the screen. This is ergonomically superior for one-handed phone usage because the bottom of the screen is within easy thumb reach, unlike the top where hamburger menus typically live. Google's Material Design guidelines recommend bottom navigation for mobile apps with three to five top-level destinations.
In responsive web design, you can show a bottom navigation on mobile while using a traditional top navigation on desktop. This dual-navigation approach provides the best experience for each device type.
Bottom Navigation Bar for Mobile
.bottom-nav {
display: none; /* Hidden on desktop */
}
@media (max-width: 768px) {
.bottom-nav {
display: flex;
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: var(--bg-white);
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1);
z-index: 1000;
padding: 0.5rem 0;
/* Account for safe area on notched phones */
padding-bottom: calc(0.5rem + env(safe-area-inset-bottom));
}
.bottom-nav-item {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
gap: 0.25rem;
padding: 0.5rem;
text-decoration: none;
color: var(--text-light);
font-size: 0.7rem;
transition: color 0.2s ease;
border: none;
background: none;
cursor: pointer;
}
.bottom-nav-item.is-active {
color: var(--primary);
}
.bottom-nav-item .icon {
font-size: 1.25rem;
}
.bottom-nav-item .label {
font-size: 0.625rem;
text-transform: uppercase;
letter-spacing: 0.05em;
}
/* Ensure page content does not hide behind bottom nav */
body {
padding-bottom: 70px;
}
}
env(safe-area-inset-bottom) in your bottom navigation padding to handle devices with home indicator bars or notches. This CSS environment variable provides the safe area inset, preventing your navigation from being obscured by hardware elements on modern phones.Sticky Navigation on Scroll
Sticky navigation keeps the header fixed at the top of the viewport as the user scrolls down the page. This ensures the navigation is always accessible without requiring the user to scroll back to the top. The CSS position: sticky property makes this trivially easy to implement, though there are advanced patterns like hiding the nav on scroll down and showing it on scroll up that require JavaScript.
Sticky Navigation with Scroll Effects
/* Simple sticky header */
.site-header {
position: sticky;
top: 0;
z-index: 100;
background: var(--bg-white);
transition: box-shadow 0.3s ease, padding 0.3s ease;
}
/* Add shadow when scrolled (via JavaScript adding a class) */
.site-header.is-scrolled {
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
padding-top: 0.5rem;
padding-bottom: 0.5rem;
}
/* Hide on scroll down, show on scroll up pattern */
.site-header.is-hidden {
transform: translateY(-100%);
}
.site-header {
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
/* Compact mode on scroll -- shrink logo and padding */
.site-header .logo {
height: 40px;
transition: height 0.3s ease;
}
.site-header.is-scrolled .logo {
height: 30px;
}
/* On mobile, sticky nav takes less space */
@media (max-width: 768px) {
.site-header {
padding: 0.5rem 1rem;
}
.site-header.is-scrolled {
padding: 0.25rem 1rem;
}
}
Breadcrumb Navigation on Small Screens
Breadcrumbs help users understand their location within a site's hierarchy. On desktop, a full breadcrumb trail is easy to display. On mobile, breadcrumbs can become problematic when the path is long. There are several responsive strategies for handling breadcrumbs on small screens: truncating middle items with an ellipsis, showing only the parent page, or enabling horizontal scrolling.
Responsive Breadcrumb Patterns
/* Full breadcrumb on desktop */
.breadcrumb {
display: flex;
flex-wrap: wrap;
list-style: none;
margin: 0;
padding: 1rem 0;
font-size: 0.875rem;
gap: 0;
}
.breadcrumb li {
display: flex;
align-items: center;
}
.breadcrumb li + li::before {
content: "/";
margin: 0 0.5rem;
color: var(--text-light);
}
.breadcrumb a {
color: var(--primary);
text-decoration: none;
}
.breadcrumb a:hover {
text-decoration: underline;
}
/* Mobile: horizontal scroll approach */
@media (max-width: 768px) {
.breadcrumb {
flex-wrap: nowrap;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
scrollbar-width: none; /* Firefox */
padding-bottom: 0.5rem;
}
.breadcrumb::-webkit-scrollbar {
display: none; /* Chrome, Safari */
}
.breadcrumb li {
flex-shrink: 0;
white-space: nowrap;
}
}
/* Alternative: show only parent and current on mobile */
@media (max-width: 480px) {
.breadcrumb-minimal li {
display: none;
}
.breadcrumb-minimal li:first-child,
.breadcrumb-minimal li:nth-last-child(2),
.breadcrumb-minimal li:last-child {
display: flex;
}
/* Ellipsis between first and parent */
.breadcrumb-minimal li:first-child + li:not(:nth-last-child(2))::before {
content: "...";
margin: 0 0.5rem;
}
}
Tab Navigation with Horizontal Scrolling on Mobile
Tab navigation presents a row of tabs that switch between different content panels. On desktop, all tabs fit comfortably in a row. On mobile, if there are many tabs, they need to scroll horizontally. This pattern is widely used in app interfaces, product category pages, and settings panels. The key is to provide a smooth scrolling experience with visual indicators that more tabs exist beyond the visible area.
Scrollable Tab Navigation
.tab-nav {
display: flex;
border-bottom: 2px solid var(--border-light);
position: relative;
}
.tab-nav-list {
display: flex;
list-style: none;
margin: 0;
padding: 0;
width: 100%;
}
.tab-button {
flex: 1;
padding: 1rem 1.5rem;
background: none;
border: none;
border-bottom: 2px solid transparent;
margin-bottom: -2px;
cursor: pointer;
font-weight: 500;
color: var(--text-light);
transition: color 0.2s ease, border-color 0.2s ease;
white-space: nowrap;
}
.tab-button:hover {
color: var(--text-dark);
}
.tab-button.is-active {
color: var(--primary);
border-bottom-color: var(--primary);
}
/* Mobile: scrollable tabs */
@media (max-width: 768px) {
.tab-nav {
overflow: hidden;
position: relative;
}
.tab-nav-list {
overflow-x: auto;
-webkit-overflow-scrolling: touch;
scrollbar-width: none;
scroll-snap-type: x mandatory;
}
.tab-nav-list::-webkit-scrollbar {
display: none;
}
.tab-button {
flex: none;
padding: 0.75rem 1.25rem;
scroll-snap-align: start;
}
/* Fade edges to indicate scrollability */
.tab-nav::after {
content: "";
position: absolute;
right: 0;
top: 0;
bottom: 0;
width: 40px;
background: linear-gradient(to right, transparent, var(--bg-white));
pointer-events: none;
}
}
Mega Menus on Mobile
Mega menus are large dropdown panels that display grouped navigation links, images, and promotional content. They work beautifully on desktop where there is ample screen space, but they need a completely different approach on mobile. The typical mobile adaptation is to convert the mega menu into an accordion-style expandable menu, where each top-level category reveals its sub-items when tapped.
Mega Menu: Desktop Dropdown to Mobile Accordion
/* Desktop mega menu */
.mega-menu-item {
position: relative;
}
.mega-panel {
position: absolute;
top: 100%;
left: 0;
width: 100vw;
max-width: 1200px;
background: var(--bg-white);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
padding: 2rem;
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 2rem;
opacity: 0;
visibility: hidden;
transform: translateY(-10px);
transition: opacity 0.2s ease, transform 0.2s ease, visibility 0.2s ease;
}
.mega-menu-item:hover .mega-panel,
.mega-menu-item:focus-within .mega-panel {
opacity: 1;
visibility: visible;
transform: translateY(0);
}
.mega-panel-section h3 {
font-size: 0.875rem;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--text-light);
margin-bottom: 1rem;
}
.mega-panel-section ul {
list-style: none;
padding: 0;
margin: 0;
}
.mega-panel-section a {
display: block;
padding: 0.375rem 0;
color: var(--text-dark);
text-decoration: none;
}
.mega-panel-section a:hover {
color: var(--primary);
}
/* Mobile: convert to accordion */
@media (max-width: 768px) {
.mega-panel {
position: static;
display: none;
grid-template-columns: 1fr;
box-shadow: none;
padding: 0 0 0 1rem;
gap: 0;
transform: none;
opacity: 1;
visibility: visible;
background: var(--bg-light);
}
.mega-menu-item.is-expanded .mega-panel {
display: block;
}
.mega-trigger {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
padding: 1rem 1.5rem;
background: none;
border: none;
border-bottom: 1px solid var(--border-light);
cursor: pointer;
}
.mega-trigger::after {
content: "+";
font-size: 1.25rem;
transition: transform 0.2s ease;
}
.mega-menu-item.is-expanded .mega-trigger::after {
content: "-";
}
.mega-panel-section {
padding: 0.75rem 0;
border-bottom: 1px solid var(--border-light);
}
}
Accessible Navigation: ARIA, Keyboard & Focus Management
Responsive navigation must be accessible to all users, including those who navigate with keyboards, screen readers, or other assistive technologies. Accessibility is not an optional enhancement -- it is a core requirement. Here are the essential accessibility practices for responsive navigation:
ARIA Attributes for Toggle Menus
When you have a hamburger button that toggles navigation visibility, you must communicate the state to assistive technologies using ARIA attributes. The aria-expanded attribute on the toggle button tells screen readers whether the menu is currently open or closed. The aria-controls attribute establishes the relationship between the button and the navigation it controls. The aria-label attribute on the <nav> element helps distinguish it from other navigation landmarks on the page.
Essential ARIA Attributes for Navigation
/* HTML with ARIA attributes:
<button class="nav-toggle"
aria-expanded="false"
aria-controls="main-nav"
aria-label="Toggle main menu">
<span class="sr-only">Menu</span>
</button>
<nav id="main-nav" aria-label="Main navigation">
<ul role="list">
<li><a href="/" aria-current="page">Home</a></li>
</ul>
</nav>
When opened via JavaScript:
button.setAttribute("aria-expanded", "true");
When closed:
button.setAttribute("aria-expanded", "false");
*/
/* Screen-reader-only utility class */
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border-width: 0;
}
/* Focus styles for keyboard navigation */
.nav-list a:focus-visible {
outline: 2px solid var(--primary);
outline-offset: 2px;
border-radius: 2px;
}
.nav-toggle:focus-visible {
outline: 2px solid var(--primary);
outline-offset: 2px;
}
Keyboard Navigation and Focus Trapping
When an off-canvas or overlay navigation is open, focus should be trapped within it so that keyboard users cannot tab to elements hidden behind the overlay. When the menu is closed, focus should return to the toggle button. The Escape key should close the menu. Tab and Shift+Tab should cycle through the menu items. These behaviors must be managed with JavaScript, but the CSS foundation includes visible focus indicators and proper styling for the focused state.
Focus Management CSS
/* Remove default focus outline but provide custom visible focus */
.main-nav a:focus {
outline: none;
}
.main-nav a:focus-visible {
outline: 2px solid var(--primary);
outline-offset: 2px;
background: var(--primary-light);
}
/* When nav is closed on mobile, prevent focus on hidden items */
@media (max-width: 768px) {
.main-nav:not(.is-open) {
visibility: hidden;
}
.main-nav.is-open {
visibility: visible;
}
}
/* Skip to main content link -- visible on focus only */
.skip-link {
position: absolute;
top: -100%;
left: 50%;
transform: translateX(-50%);
background: var(--primary);
color: white;
padding: 0.75rem 1.5rem;
border-radius: 0 0 4px 4px;
z-index: 9999;
text-decoration: none;
transition: top 0.2s ease;
}
.skip-link:focus {
top: 0;
}
visibility: hidden instead of display: none on the closed navigation allows CSS transitions to work. With display: none, the element immediately disappears with no transition. With visibility: hidden, you can combine it with opacity and transform transitions for smooth open and close animations. Both methods correctly remove hidden elements from the tab order.Transitions for Menu Open and Close
Smooth transitions make navigation feel polished and responsive. Without transitions, menus snap open and closed instantaneously, which can feel jarring and disorienting. A well-crafted transition gives users visual feedback about what changed and where to look. Keep menu transitions short -- between 200 and 300 milliseconds. Longer transitions feel sluggish, and shorter ones feel too abrupt.
Smooth Menu Transition Patterns
/* Slide from right */
.slide-right {
transform: translateX(100%);
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.slide-right.is-open {
transform: translateX(0);
}
/* Slide from top with fade */
.slide-down {
opacity: 0;
transform: translateY(-20px);
transition: opacity 0.25s ease, transform 0.25s ease;
}
.slide-down.is-open {
opacity: 1;
transform: translateY(0);
}
/* Scale up from a point */
.scale-in {
opacity: 0;
transform: scale(0.95);
transform-origin: top right;
transition: opacity 0.2s ease, transform 0.2s ease;
}
.scale-in.is-open {
opacity: 1;
transform: scale(1);
}
/* Staggered child animations */
.stagger-nav .nav-link {
opacity: 0;
transform: translateX(-20px);
transition: opacity 0.3s ease, transform 0.3s ease;
}
.stagger-nav.is-open .nav-link:nth-child(1) { transition-delay: 0.05s; }
.stagger-nav.is-open .nav-link:nth-child(2) { transition-delay: 0.1s; }
.stagger-nav.is-open .nav-link:nth-child(3) { transition-delay: 0.15s; }
.stagger-nav.is-open .nav-link:nth-child(4) { transition-delay: 0.2s; }
.stagger-nav.is-open .nav-link:nth-child(5) { transition-delay: 0.25s; }
.stagger-nav.is-open .nav-link {
opacity: 1;
transform: translateX(0);
}
Complete Implementation: Responsive Navigation System
Let us put everything together into a complete, production-ready responsive navigation system. This example combines a horizontal desktop nav, a slide-out mobile hamburger menu, a sticky header, proper ARIA attributes, focus management styles, and smooth transitions. It demonstrates best practices for structure, styling, and accessibility in a single cohesive implementation.
Complete Responsive Navigation System
/* ===== Base Reset for Navigation ===== */
.site-header {
position: sticky;
top: 0;
z-index: 100;
background: var(--bg-white);
border-bottom: 1px solid var(--border-light);
transition: box-shadow 0.3s ease;
}
.site-header.is-scrolled {
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
}
.header-inner {
display: flex;
align-items: center;
justify-content: space-between;
max-width: 1200px;
margin: 0 auto;
padding: 0 1.5rem;
height: 64px;
}
.logo {
font-size: 1.25rem;
font-weight: 700;
text-decoration: none;
color: var(--text-dark);
z-index: 1001;
}
/* ===== Desktop Navigation ===== */
.main-nav-list {
display: flex;
list-style: none;
margin: 0;
padding: 0;
gap: 0.5rem;
}
.main-nav-list a {
display: block;
padding: 0.5rem 1rem;
text-decoration: none;
color: var(--text-dark);
border-radius: 6px;
font-weight: 500;
transition: background 0.2s ease, color 0.2s ease;
}
.main-nav-list a:hover {
background: var(--bg-light);
}
.main-nav-list a.is-active {
color: var(--primary);
background: var(--primary-light);
}
.main-nav-list a:focus-visible {
outline: 2px solid var(--primary);
outline-offset: 2px;
}
/* ===== Hamburger Button (hidden on desktop) ===== */
.hamburger-btn {
display: none;
background: none;
border: none;
cursor: pointer;
padding: 0.5rem;
z-index: 1001;
}
.hamburger-icon {
display: block;
width: 24px;
height: 2px;
background: var(--text-dark);
position: relative;
transition: background 0.3s ease;
}
.hamburger-icon::before,
.hamburger-icon::after {
content: "";
position: absolute;
left: 0;
width: 24px;
height: 2px;
background: var(--text-dark);
transition: transform 0.3s ease;
}
.hamburger-icon::before { top: -7px; }
.hamburger-icon::after { top: 7px; }
/* X animation */
.hamburger-btn[aria-expanded="true"] .hamburger-icon {
background: transparent;
}
.hamburger-btn[aria-expanded="true"] .hamburger-icon::before {
transform: rotate(45deg) translate(5px, 5px);
}
.hamburger-btn[aria-expanded="true"] .hamburger-icon::after {
transform: rotate(-45deg) translate(5px, -5px);
}
/* ===== Mobile Navigation ===== */
@media (max-width: 768px) {
.hamburger-btn {
display: block;
}
.main-nav {
position: fixed;
top: 0;
right: 0;
width: min(320px, 85vw);
height: 100vh;
height: 100dvh;
background: var(--bg-white);
transform: translateX(100%);
transition: transform 0.35s cubic-bezier(0.4, 0, 0.2, 1),
visibility 0.35s ease;
visibility: hidden;
z-index: 1000;
overflow-y: auto;
padding: 5rem 0 2rem;
}
.main-nav.is-open {
transform: translateX(0);
visibility: visible;
}
.main-nav-list {
flex-direction: column;
gap: 0;
}
.main-nav-list a {
padding: 1rem 1.5rem;
border-radius: 0;
border-bottom: 1px solid var(--border-light);
font-size: 1.1rem;
}
/* Overlay */
.nav-backdrop {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.4);
z-index: 999;
opacity: 0;
visibility: hidden;
transition: opacity 0.3s ease, visibility 0.3s ease;
}
.nav-backdrop.is-visible {
opacity: 1;
visibility: visible;
}
}
/* ===== Skip Link ===== */
.skip-to-content {
position: absolute;
top: -100%;
left: 1rem;
background: var(--primary);
color: #fff;
padding: 0.75rem 1.5rem;
border-radius: 0 0 6px 6px;
z-index: 9999;
font-weight: 600;
text-decoration: none;
}
.skip-to-content:focus {
top: 0;
}
100dvh (dynamic viewport height) instead of 100vh for mobile navigation height. The 100vh unit does not account for the browser address bar on mobile devices, which can cause the navigation to extend behind it. The dvh unit adjusts to the actual visible viewport. Provide 100vh as a fallback for older browsers: set height: 100vh first, then height: 100dvh on the next line.visibility: hidden on the closed mobile navigation. If you only use transform: translateX(100%) to move it off screen, the links are still focusable via keyboard even though they are invisible. Always combine transforms with visibility to properly remove hidden elements from the accessibility tree and tab order.Practice Exercise
Build a complete responsive navigation system from scratch. Start with a horizontal desktop navigation bar containing at least six links. Implement a hamburger menu that slides in from the right on screens narrower than 768 pixels, with the hamburger icon animating into an X shape when opened. Add a semi-transparent backdrop overlay behind the mobile menu. Include proper ARIA attributes on the toggle button (aria-expanded, aria-controls) and update them with JavaScript. Add a skip-to-content link that appears only on keyboard focus. Make the header sticky with a subtle box shadow that appears after scrolling. Finally, add staggered entrance animations to the mobile navigation items so they slide in one after another when the menu opens. Test your implementation by navigating the entire menu using only the keyboard, and verify that the Escape key closes the mobile menu.