How to Create a Responsive Navbar in CSS
This recipe builds a navbar that shows a horizontal menu on wide screens and collapses into a toggleable hamburger menu on small screens, using flexbox and a media query.
What responsive means for a navbar
The common pattern is: above a chosen breakpoint, links sit in a horizontal row; below it, the links hide and a hamburger button takes their place. Tapping the hamburger reveals the links again, usually as a full-width panel. Getting this right needs three pieces working together: a CSS layout for the wide view, a media query that swaps to the narrow view, and a small script that toggles the narrow menu open and closed.
The full example
The markup stays plain: a nav element, an unordered list of links, and a real button for the toggle (not a div - buttons are focusable and clickable with the keyboard for free). The CSS lays the nav out with flexbox, and a media query hides the link list and reveals the button below the breakpoint. A few lines of JavaScript flip a class on click.
Example
<!DOCTYPE html>
<html>
<head>
<style>
* { box-sizing: border-box; }
body { margin: 0; font-family: sans-serif; }
.navbar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px 24px;
background: #1f2937;
color: white;
}
.brand { font-weight: bold; font-size: 1.2rem; }
.nav-toggle {
display: none;
background: none;
border: none;
color: white;
font-size: 1.5rem;
cursor: pointer;
}
.nav-links {
display: flex;
gap: 20px;
list-style: none;
margin: 0;
padding: 0;
}
.nav-links a {
color: white;
text-decoration: none;
}
@media (max-width: 700px) {
.nav-toggle { display: block; }
.nav-links {
display: none;
flex-direction: column;
width: 100%;
position: absolute;
top: 60px;
left: 0;
background: #1f2937;
padding: 16px 24px;
}
.nav-links.open {
display: flex;
}
}
</style>
</head>
<body>
<nav class='navbar'>
<span class='brand'>MySite</span>
<ul class='nav-links' id='navLinks'>
<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>
<button class='nav-toggle' id='navToggle' aria-expanded='false' aria-controls='navLinks'>☰</button>
</nav>
<script>
const toggle = document.getElementById('navToggle');
const links = document.getElementById('navLinks');
toggle.addEventListener('click', () => {
const isOpen = links.classList.toggle('open');
toggle.setAttribute('aria-expanded', isOpen);
});
</script>
</body>
</html>How the breakpoint works
Above the breakpoint, .nav-links is a normal flex row and .nav-toggle stays hidden - there is nothing to toggle because the links are already visible. At or below the breakpoint, the media query hides .nav-links by default and shows the button instead; clicking the button adds an open class that the same media query turns back into display: flex. Nothing changes above the breakpoint because the open class only has an effect inside the query.
Example
<!DOCTYPE html>
<html>
<head>
<style>
/* Above 700px: links show as a row, the hamburger is hidden */
.nav-links { display: flex; gap: 20px; }
.nav-toggle { display: none; }
/* At or below 700px: hide the row, show the hamburger instead */
@media (max-width: 700px) {
.nav-toggle { display: block; }
.nav-links { display: none; }
.nav-links.open { display: flex; flex-direction: column; }
}
</style>
</head>
<body>
<nav class="navbar">
<ul class="nav-links">
<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>
<button class="nav-toggle">☰</button>
</nav>
</body>
</html>- Keep aria-expanded on the button in sync with the open or closed state, so screen readers announce the change.
- Point aria-controls at the id of the menu the button toggles.
- Keep tap targets at least about 44px tall on mobile so links are easy to hit with a thumb.
Two common variations are worth knowing. A pure-CSS checkbox hack achieves the same open and close behavior with a hidden checkbox and a label styled as the button, avoiding JavaScript entirely, but it is semantically odd and generally less accessible. A slide-in side panel is the same idea with a transform-based animation instead of a simple display toggle.
Frequently Asked Questions
- How do I make a navbar responsive without JavaScript?
- Use a media query to switch the flex direction from row to column below your breakpoint. For a genuine toggle you need a little JavaScript, or a hidden checkbox paired with a label, which flips the menu open using CSS alone.
- What is the best breakpoint for a mobile navbar?
- Around 768px is the common choice, but pick the width where your own links start to wrap rather than copying a number. Resize the browser until the layout breaks, and set the breakpoint there.