Navbar & Header Patterns
Almost every site needs a navigation bar, and almost every navigation bar needs to do the same handful of things: line up a logo and some links horizontally, stay usable on a phone-sized screen, and often stay visible while the page scrolls. None of that requires anything exotic — Flexbox, a checkbox hack for no-JS collapsing, and position: sticky cover the vast majority of real navbars. This page collects the recipes.
A horizontal nav with Flexbox
The bread-and-butter navbar layout is a flex container with the logo on one side and the link list on the other. justify-content: space-between does the heavy lifting — it pushes the first and last flex items to opposite ends and lets Flexbox handle the rest.
<header class="navbar">
<a class="navbar__logo" href="/">Brand</a>
<nav>
<ul class="navbar__links">
<li><a href="/">Home</a></li>
<li><a href="/docs">Docs</a></li>
<li><a href="/pricing">Pricing</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
</nav>
</header>.navbar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.75rem 1.5rem;
background: #ffffff;
border-bottom: 1px solid #e5e7eb;
}
.navbar__logo {
font-weight: 700;
font-size: 1.25rem;
text-decoration: none;
color: #111827;
}
.navbar__links {
display: flex;
align-items: center;
gap: 1.5rem;
margin: 0;
padding: 0;
list-style: none;
}
.navbar__links a {
text-decoration: none;
color: #374151;
font-weight: 500;
}
.navbar__links a:hover {
color: #111827;
}Collapsing into a hamburger menu (CSS-only)
On narrow viewports there usually isn't room for a full link list, so the classic move is to hide the links behind a "hamburger" icon. A pure-CSS version of this uses a hidden checkbox plus a <label> styled as the icon: checking the box (by clicking the label) toggles a sibling's visibility through the :checked pseudo-class and the general sibling combinator — no JavaScript required.
<header class="navbar">
<a class="navbar__logo" href="/">Brand</a>
<input type="checkbox" id="nav-toggle" class="navbar__toggle-input" />
<label for="nav-toggle" class="navbar__toggle-label" aria-label="Menu">
☰
</label>
<nav class="navbar__menu">
<ul class="navbar__links">
<li><a href="/">Home</a></li>
<li><a href="/docs">Docs</a></li>
<li><a href="/pricing">Pricing</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
</nav>
</header>.navbar__toggle-input {
display: none;
}
.navbar__toggle-label {
display: none;
font-size: 1.5rem;
cursor: pointer;
}
@media (width < 768px) {
.navbar__toggle-label {
display: block;
}
.navbar__menu {
display: none;
width: 100%;
order: 3;
}
/* When the checkbox is checked, show the sibling menu */
.navbar__toggle-input:checked ~ .navbar__menu {
display: block;
}
.navbar {
flex-wrap: wrap;
}
.navbar__links {
flex-direction: column;
align-items: flex-start;
gap: 0.75rem;
padding: 1rem 0;
}
}aria-expanded on a real <button> does, and keyboard users can only reach it via tab order rather than the Enter/Space activation pattern they expect from a menu button. A production navbar typically pairs a real <button aria-expanded aria-controls> with a small amount of JavaScript to toggle a class and manage focus, using the CSS above purely for the visual collapse/expand.Sticky header on scroll
Keeping the navbar visible while the page scrolls is just position: sticky with an offset (usually top: 0) — the full mechanics are covered on the position: sticky page. The one navbar-specific detail is that a sticky header needs an opaque background and a sensible z-index, otherwise content scrolling underneath will show through or overlap it.
.navbar {
position: sticky;
top: 0;
z-index: 100;
background: #ffffff; /* opaque — content must not show through */
}Putting it together
A complete, responsive, sticky navbar combines all three techniques: Flexbox for the row layout, the checkbox toggle for the mobile collapse, and position: sticky to keep it on screen.
.navbar {
position: sticky;
top: 0;
z-index: 100;
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
padding: 0.75rem 1.5rem;
background: #ffffff;
border-bottom: 1px solid #e5e7eb;
}
.navbar__toggle-input {
display: none;
}
.navbar__toggle-label {
display: none;
font-size: 1.5rem;
cursor: pointer;
}
@media (width < 768px) {
.navbar__toggle-label {
display: block;
}
.navbar__menu {
display: none;
width: 100%;
order: 3;
}
.navbar__toggle-input:checked ~ .navbar__menu {
display: block;
}
}Quick reference
Pattern | Core CSS | When to reach for it |
|---|---|---|
Logo + links row |
| Standard desktop navbar layout |
Hamburger collapse | Hidden checkbox + | Mobile menu with zero JavaScript for the visual toggle |
Stays on screen |
| Header should remain visible while the page scrolls |
Flexbox handles the row layout — logo and links on opposite ends.
The checkbox/label hack collapses the menu without JavaScript, but is not a substitute for proper ARIA attributes and keyboard handling.
position: sticky(recap: position: sticky) keeps the header visible on scroll — give it an opaque background and az-index.
aria-expanded. Treat the CSS-only techniques here as the visual foundation, not the whole accessibility story.