Building a Responsive Navigation Bar with Flexbox and CSS Grid
A navigation bar is the first thing users see when they land on a site. If it looks cramped on a phone or stretches awkwardly on a widescreen, you’ve already lost half the battle for attention. In 2024, with a sea of devices ranging from 320‑pixel phones to 4K monitors, a responsive nav isn’t a nice‑to‑have—it’s a must. Let’s walk through a clean, modern approach that blends Flexbox’s simplicity with CSS Grid’s power, and end up with a bar that works everywhere without a single JavaScript hack.
Why Flexbox and Grid Together?
Both Flexbox and CSS Grid are layout modules, but they shine in different scenarios.
- Flexbox is one‑dimensional. It excels at aligning items along a single row or column, making it perfect for the typical “logo on the left, links on the right” pattern.
- CSS Grid is two‑dimensional. It lets you define rows and columns simultaneously, which becomes handy when you need to rearrange the whole navigation structure for smaller screens.
By starting with Flexbox for the desktop layout and then switching to Grid for the mobile breakpoint, we get the best of both worlds: minimal code, clear intent, and smooth transitions.
The HTML Skeleton
First, let’s set up a semantic markup. I like to keep the nav inside a <header> so screen readers can quickly locate the site’s primary navigation.
<header class="site-header">
<nav class="main-nav" aria-label="Primary">
<a href="/" class="logo">CodeCraft Hub</a>
<ul class="nav-list">
<li><a href="/tutorials">Tutorials</a></li>
<li><a href="/blog">Blog</a></li>
<li><a href="/about">About</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
<button class="nav-toggle" aria-expanded="false" aria-controls="nav-list">
☰
</button>
</nav>
</header>
A few notes:
- The
<ul>holds the navigation links – this is the most accessible pattern. - The toggle button will appear only on small screens; it’s hidden by default with CSS.
- I’ve added
aria-expandedandaria-controlsto keep it screen‑reader friendly. (We’ll add a tiny bit of JavaScript later just to flip the attribute – no heavy frameworks needed.)
Desktop Layout with Flexbox
On larger viewports we want the logo on the left, the list of links centered or right‑aligned, and the toggle button hidden. Here’s the Flexbox recipe:
/* Base styles */
.site-header {
background: #0d1117;
color: #c9d1d9;
}
/* Flex container */
.main-nav {
display: flex;
align-items: center;
justify-content: space-between;
max-width: 1200px;
margin: 0 auto;
padding: 0.5rem 1rem;
}
/* Logo */
.logo {
font-size: 1.5rem;
font-weight: bold;
text-decoration: none;
color: inherit;
}
/* Nav list */
.nav-list {
display: flex;
gap: 1.5rem;
list-style: none;
margin: 0;
padding: 0;
}
/* Links */
.nav-list a {
text-decoration: none;
color: inherit;
font-weight: 500;
}
/* Hide toggle on desktop */
.nav-toggle {
display: none;
}
What’s happening?
display: flexon.main-navcreates a horizontal line.justify-content: space-betweenpushes the logo to the far left and the nav list to the far right.- The
.nav-listitself is also a flex container, giving us even spacing (gap) between each link without extra margins.
At this point, the bar looks crisp on a laptop or desktop. Resize the window and you’ll notice the links start to squish – that’s where the responsive switch comes in.
Switching to Grid for Mobile
When the viewport drops below, say, 768 px, we want the navigation to stack: logo on top, toggle button visible, and the menu items either hidden or displayed in a vertical list. Grid makes this rearrangement straightforward.
@media (max-width: 768px) {
.main-nav {
display: grid;
grid-template-areas:
"logo toggle"
"menu menu";
grid-template-columns: 1fr auto;
align-items: center;
gap: 0.5rem;
}
.logo {
grid-area: logo;
}
.nav-toggle {
grid-area: toggle;
display: block;
background: none;
border: none;
font-size: 1.5rem;
color: inherit;
cursor: pointer;
}
.nav-list {
grid-area: menu;
display: none; /* hidden by default */
flex-direction: column; /* stack links vertically */
gap: 0.75rem;
background: #161b22;
padding: 1rem;
border-radius: 4px;
}
.nav-list.show {
display: flex; /* show when toggle is active */
}
}
Key points:
grid-template-areaslets us name sections of the layout. The logo and toggle share the first row, while the menu occupies the second row spanning both columns.- The
.nav-listswitches from a horizontal flex row to a vertical column (flex-direction: column) when displayed. - We hide the list by default (
display: none) and reveal it with a.showclass that JavaScript will toggle.
The Tiny JavaScript Toggle
Even though the post is about CSS, a minimal script is required to make the hamburger button functional. Keep it vanilla – no dependencies.
document.addEventListener('DOMContentLoaded', function () {
const toggle = document.querySelector('.nav-toggle');
const menu = document.querySelector('.nav-list');
toggle.addEventListener('click', function () {
const expanded = toggle.getAttribute('aria-expanded') === 'true' || false;
toggle.setAttribute('aria-expanded', !expanded);
menu.classList.toggle('show');
});
});
What this does:
- Waits for the DOM to load.
- Finds the button and the list.
- On click, flips
aria-expanded(helps assistive tech) and toggles the.showclass, which switches the menu’sdisplayfromnonetoflex.
That’s it – no jQuery, no React, just plain JavaScript.
Polishing the Experience
A few extra touches make the nav feel professional:
- Transition – add a subtle fade when the menu appears.
.nav-list {
transition: opacity 0.3s ease;
opacity: 0;
}
.nav-list.show {
opacity: 1;
}
- Focus styles – ensure keyboard users can see which link is active.
.nav-list a:focus {
outline: 2px solid #58a6ff;
outline-offset: 2px;
}
- Dark mode awareness – if your site supports a dark theme, use CSS variables for colors so the nav adapts automatically.
:root {
--bg: #0d1117;
--text: #c9d1d9;
}
[data-theme="dark"] {
--bg: #0d1117;
--text: #c9d1d9;
}
.site-header { background: var(--bg); color: var(--text); }
Testing on Real Devices
I can’t stress enough how valuable real‑device testing is. Emulators are great, but they sometimes hide subtle touch‑target issues. Grab a phone, open the dev tools, and verify:
- The toggle button is large enough to tap comfortably (at least 44 px square).
- Links have enough spacing to avoid accidental clicks.
- The transition feels snappy, not laggy.
If anything feels off, tweak the gap or padding values – Flexbox and Grid are forgiving, so small adjustments go a long way.
Wrap‑Up
Building a responsive navigation bar doesn’t have to be a maze of media queries and JavaScript gymnastics. By letting Flexbox handle the straightforward desktop row and handing the mobile rearrangement over to CSS Grid, you keep the code readable and the intent clear. The result is a navigation component that feels native on any screen, stays accessible, and requires only a handful of lines of CSS and a tiny script.
Give it a spin in your next project, and you’ll see why I keep returning to this pattern for every new site I launch. Happy coding!
- → Modern CSS Techniques: Using Variables and Calc for Dynamic Layouts
- → Debugging Common Frontend Bugs: A Checklist for Developers
- → Design Systems 101: Creating Reusable Components with Styled‑Components
- → Performance Audits with Lighthouse: Interpreting Scores and Fixing Issues
- → From Prototype to Production: Managing State in Large-Scale JavaScript Projects