Responsive Navbar with HTML, CSS, and JavaScript: Accessible Mobile Menu
• Updated 6/1/2026 • responsive navbarresponsive navigation menuHTMLCSSJavaScriptvanilla JavaScripthamburger menufrontendaccessibilityUI component
One-file front-end editor
Write plain HTML, CSS, JavaScript, or CDN-powered Vue and React demos.
Build a Responsive Navbar with HTML, CSS, and Vanilla JavaScript
A responsive navigation bar is one of the most reusable UI components in a real website. This beginner-friendly snippet creates a clean header that displays navigation links horizontally on larger screens and switches to an accessible hamburger menu on smaller screens.
Practical use case
Use this component for landing pages, portfolio sites, SaaS dashboards, small business websites, and documentation pages where visitors need simple navigation that works on both desktop and mobile devices without a framework.
What you will build
- A semantic header and navigation region for better structure and accessibility.
- A mobile menu button with
aria-expandedandaria-controlsattributes. - A responsive layout powered by CSS Flexbox and one media query.
- Small, reusable JavaScript behavior that opens, closes, and resets the menu correctly.
Complete copy-ready example
Create a file named index.html, paste in the code below, and open it in a browser. Resize the browser window to see the desktop menu change into a mobile toggle button.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Responsive Navbar Example</title>
<style>
:root {
--nav-bg: #0f172a;
--nav-text: #f8fafc;
--nav-muted: #cbd5e1;
--nav-hover: #1e293b;
--nav-focus: #38bdf8;
--page-bg: #f8fafc;
--content-text: #0f172a;
--max-width: 1120px;
}
*,
*::before,
*::after {
box-sizing: border-box;
}
body {
margin: 0;
min-height: 100vh;
background: var(--page-bg);
color: var(--content-text);
font-family: Arial, sans-serif;
}
.site-header {
background: var(--nav-bg);
color: var(--nav-text);
}
.navbar {
position: relative;
max-width: var(--max-width);
margin: 0 auto;
padding: 0 1rem;
min-height: 4rem;
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
}
.brand {
color: var(--nav-text);
font-size: 1.25rem;
font-weight: 700;
letter-spacing: 0.02em;
text-decoration: none;
}
.brand:hover {
color: var(--nav-muted);
}
.nav-toggle {
display: none;
align-items: center;
justify-content: center;
width: 2.75rem;
height: 2.75rem;
padding: 0;
border: 1px solid var(--nav-muted);
border-radius: 0.5rem;
background: transparent;
color: var(--nav-text);
cursor: pointer;
}
.nav-toggle:hover {
background: var(--nav-hover);
}
.nav-toggle:focus-visible,
.brand:focus-visible,
.nav-link:focus-visible {
outline: 3px solid var(--nav-focus);
outline-offset: 2px;
}
.hamburger,
.hamburger::before,
.hamburger::after {
display: block;
width: 1.35rem;
height: 2px;
border-radius: 999px;
background: currentColor;
transition: transform 0.2s ease, opacity 0.2s ease;
}
.hamburger {
position: relative;
}
.hamburger::before,
.hamburger::after {
position: absolute;
left: 0;
content: "";
}
.hamburger::before {
transform: translateY(-0.42rem);
}
.hamburger::after {
transform: translateY(0.42rem);
}
.nav-toggle[aria-expanded="true"] .hamburger {
background: transparent;
}
.nav-toggle[aria-expanded="true"] .hamburger::before {
transform: rotate(45deg);
}
.nav-toggle[aria-expanded="true"] .hamburger::after {
transform: rotate(-45deg);
}
.nav-menu {
display: flex;
align-items: center;
gap: 0.25rem;
margin: 0;
padding: 0;
list-style: none;
}
.nav-link {
display: block;
padding: 0.75rem 0.9rem;
border-radius: 0.4rem;
color: var(--nav-text);
text-decoration: none;
}
.nav-link:hover,
.nav-link[aria-current="page"] {
background: var(--nav-hover);
}
.page-content {
max-width: var(--max-width);
margin: 0 auto;
padding: 3rem 1rem;
}
@media (max-width: 48rem) {
.nav-toggle {
display: inline-flex;
}
.nav-menu {
display: none;
position: absolute;
top: calc(100% - 0.25rem);
left: 1rem;
right: 1rem;
z-index: 10;
flex-direction: column;
align-items: stretch;
gap: 0.25rem;
padding: 0.5rem;
border-radius: 0.5rem;
background: var(--nav-bg);
box-shadow: 0 0.5rem 1.25rem rgba(15, 23, 42, 0.22);
}
.nav-menu.is-open {
display: flex;
}
.nav-link {
padding: 0.9rem 1rem;
}
}
</style>
</head>
<body>
<header class="site-header">
<nav class="navbar" aria-label="Main navigation">
<a class="brand" href="/">CompileQuestHub</a>
<button
class="nav-toggle"
type="button"
aria-expanded="false"
aria-controls="primary-menu"
aria-label="Open main menu"
>
<span class="hamburger" aria-hidden="true"></span>
</button>
<ul class="nav-menu" id="primary-menu">
<li><a class="nav-link" href="/" aria-current="page">Home</a></li>
<li><a class="nav-link" href="/tutorials">Tutorials</a></li>
<li><a class="nav-link" href="/snippets">Snippets</a></li>
<li><a class="nav-link" href="/contact">Contact</a></li>
</ul>
</nav>
</header>
<main class="page-content">
<h1>Responsive Navbar Demo</h1>
<p>Resize this page to test the mobile navigation menu.</p>
</main>
<script>
const toggleButton = document.querySelector(".nav-toggle");
const navMenu = document.querySelector("#primary-menu");
const mobileBreakpoint = window.matchMedia("(max-width: 48rem)");
function closeMenu() {
navMenu.classList.remove("is-open");
toggleButton.setAttribute("aria-expanded", "false");
toggleButton.setAttribute("aria-label", "Open main menu");
}
function toggleMenu() {
const isOpen = navMenu.classList.toggle("is-open");
toggleButton.setAttribute("aria-expanded", String(isOpen));
toggleButton.setAttribute(
"aria-label",
isOpen ? "Close main menu" : "Open main menu"
);
}
toggleButton.addEventListener("click", toggleMenu);
navMenu.addEventListener("click", (event) => {
if (event.target.closest(".nav-link") && mobileBreakpoint.matches) {
closeMenu();
}
});
document.addEventListener("keydown", (event) => {
if (event.key === "Escape" && navMenu.classList.contains("is-open")) {
closeMenu();
toggleButton.focus();
}
});
mobileBreakpoint.addEventListener("change", (event) => {
if (!event.matches) {
closeMenu();
}
});
</script>
</body>
</html>
How it works
- The
<nav>element identifies the site's main navigation area, while the unordered list contains standard page links. - The menu button is hidden on desktop. At widths of
48remor less, CSS displays the button and hides the link list until JavaScript applies the.is-openclass. - Clicking the button toggles the menu and updates
aria-expanded, allowing assistive technology users to understand whether the menu is open. - Pressing the Escape key while the menu is open, or selecting a mobile link, closes the menu. Resizing back to desktop also resets the mobile state.
Why this approach is useful
- No dependency required: it works with plain HTML, CSS, and JavaScript.
- Accessible interaction: the button exposes expanded state, includes a readable label, and supports keyboard closing.
- Reusable styling: CSS custom properties let you quickly apply your brand colors.
- Real-project behavior: the menu closes predictably after navigation or layout changes.
Customization notes
Change branding and links
Replace CompileQuestHub and the sample paths with your site's real pages. Use aria-current="page" only on the link for the page currently being viewed.
Adjust the mobile breakpoint
The breakpoint is set to 48rem, which is typically 768px when the browser's default font size is used. Update the value in both the CSS media query and the JavaScript matchMedia() call so they stay synchronized.
Match your design system
Update the values in :root for navigation background, text, hover, focus, and page colors. You can also change the maximum content width and spacing without rewriting the component.
Common mistakes to avoid
- Leaving out
<meta name="viewport" content="width=device-width, initial-scale=1.0">, which causes incorrect sizing on mobile devices. - Using a clickable
<div>instead of a real<button>for the hamburger menu, which hurts keyboard accessibility. - Toggling visual styles without updating
aria-expanded, causing the accessible state to be incorrect. - Changing the CSS breakpoint but forgetting to update the JavaScript media query.
- Using
href="#"in production, which does not navigate to meaningful pages and can cause unexpected scrolling.
Related practice ideas
- Add a call-to-action link such as “Sign up” and style it as a button.
- Highlight the active page automatically by comparing link URLs with
window.location.pathname. - Close the open menu when a user clicks outside of the navigation area.
- Convert the CSS into a reusable component for a React, Vue, or Laravel Blade project.
- Add a dropdown submenu while preserving keyboard navigation and accessible labels.
Summary
This responsive navbar snippet provides a practical foundation for modern websites: semantic markup, flexible responsive styling, and accessible JavaScript menu behavior. Copy it as-is for a simple project, then customize the brand, links, colors, and navigation features to match your application.

