Responsive Sticky Header Navigation
Create a responsive sticky header that adjusts upon scrolling for improved navigation experience.
Code
<!--
Responsive Sticky Header Navigation
Usage: Paste into an HTML file. Header sticks to top and updates style/size on scroll; includes accessible mobile menu.
-->
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Responsive Sticky Header Navigation</title>
<style>
:root { --bg: #fff; --fg: #111; --shadow: 0 2px 14px rgba(0,0,0,.08); --ring: 0 0 0 3px rgba(59,130,246,.35); }
* { box-sizing: border-box; }
body { margin: 0; font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; color: var(--fg); min-height: 200vh; }
a { color: inherit; }
.header {
position: sticky; top: 0; z-index: 1000;
background: var(--bg); box-shadow: var(--shadow);
transition: background-color .25s ease, color .25s ease, padding .25s ease, box-shadow .25s ease;
padding: 14px 16px;
}
.header.scrolled {
background-color: rgba(0,0,0,.82); color: #fff;
padding: 10px 16px; box-shadow: 0 10px 26px rgba(0,0,0,.22);
backdrop-filter: blur(8px);
}
.nav { max-width: 1100px; margin: 0 auto; display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.nav ul { list-style: none; padding: 0; margin: 0; display: flex; gap: 4px; align-items: center; }
.nav li a {
display: inline-flex; align-items: center; justify-content: center;
padding: 10px 12px; text-decoration: none; border-radius: 10px;
transition: background-color .2s ease, transform .2s ease;
}
.nav li a:hover { background: rgba(0,0,0,.06); }
.header.scrolled .nav li a:hover { background: rgba(255,255,255,.12); }
.nav li a:focus-visible { outline: none; box-shadow: var(--ring); }
.menu-btn {
display: none; border: 0; background: transparent; color: inherit;
padding: 10px 12px; border-radius: 10px; cursor: pointer;
}
.menu-btn:focus-visible { outline: none; box-shadow: var(--ring); }
@media (max-width: 720px) {
.menu-btn { display: inline-flex; }
.nav ul {
position: absolute; left: 12px; right: 12px; top: calc(100% + 10px);
background: inherit; color: inherit; box-shadow: var(--shadow);
border-radius: 14px; padding: 8px; flex-direction: column; align-items: stretch;
display: none; /* toggled via [data-open] */
}
.header[data-open="true"] .nav ul { display: flex; }
.nav li a { justify-content: flex-start; }
}
/* Example page spacing (optional) */
main { max-width: 1100px; margin: 0 auto; padding: 28px 16px; }
</style>
</head>
<body>
<header class="header" data-open="false">
<nav class="nav" aria-label="Primary">
<button class="menu-btn" type="button" aria-expanded="false" aria-controls="primary-nav">
Menu
</button>
<ul id="primary-nav">
<li><a href="#">Home</a></li>
<li><a href="#">About</a></li>
<li><a href="#">Services</a></li>
<li><a href="#">Contact</a></li>
</ul>
</nav>
</header>
<main>
<h1>Scroll to see the header adjust</h1>
<p>This is example content to demonstrate sticky behavior.</p>
</main>
<script>
// Efficient scroll handling: use rAF to avoid layout thrashing on rapid scroll events.
(() => {
const header = document.querySelector(".header");
const menuBtn = document.querySelector(".menu-btn");
const navList = document.getElementById("primary-nav");
if (!header || !menuBtn || !navList) return;
const SCROLL_THRESHOLD = 50;
let ticking = false;
const updateHeaderOnScroll = () => {
const isScrolled = window.scrollY > SCROLL_THRESHOLD;
header.classList.toggle("scrolled", isScrolled);
ticking = false;
};
const onScroll = () => {
if (!ticking) {
ticking = true;
requestAnimationFrame(updateHeaderOnScroll);
}
};
const setMenuOpen = (open) => {
header.dataset.open = String(open);
menuBtn.setAttribute("aria-expanded", String(open));
};
// Init + listeners
updateHeaderOnScroll();
window.addEventListener("scroll", onScroll, { passive: true });
menuBtn.addEventListener("click", () => setMenuOpen(header.dataset.open !== "true"));
// Close on outside click (mobile) to prevent stuck-open menus.
document.addEventListener("click", (e) => {
if (header.dataset.open !== "true") return;
if (!header.contains(e.target)) setMenuOpen(false);
});
// Close on Escape for accessibility.
document.addEventListener("keydown",