Section 7: Upgrading the Site Navigation
In Section 1, we built the foundational navigation: a horizontal link list on desktop with a working active state. That gets a site functional. But real business websites need two more things:
A mobile menu. On screens narrower than about 640px, a row of navigation links either overflows, wraps awkwardly, or disappears. Every business site needs a hamburger menu that opens and closes on mobile. The standard solution is for JavaScript, but we do not need it.
A dropdown for grouped links. When a business offers multiple services or has sub-pages under a section, a dropdown menu lets you organise those links without cluttering the main nav bar.
Everything in this section is built with zero JavaScript. The mobile menu uses the :checked pseudo-class selector. The dropdown uses :hover and :focus-within. The keyboard accessibility uses :focus-visible and semantic HTML.
Mobile Menu Without JavaScript — The CSS :checked Hack
The technique works by pairing a hidden <input type=”checkbox”> with a visible <label>. When the user clicks the label (the hamburger icon), the checkbox toggles its :checked state. CSS then uses the :checked pseudo-class as a conditional — showing or hide the nav panel depending on whether the checkbox is checked. The label is the button. The checkbox is the invisible state machine.
The HTML Structure
Copy the following HTML code and replace the header section of all the pages – index.html, about.html, services.html, and contact.html.
<header class="site-header">
<!--
The checkbox MUST come first in the DOM.
CSS can only reach LATER siblings with the ~ combinator.
-->
<input
type="checkbox"
id="menu-toggle"
class="menu-checkbox"
aria-hidden="true"
/>
<!-- The visible nav bar row -->
<div class="nav-inner">
<!-- Clickable logo goes here -->
<a href="index.html" class="logo">Kmacims EAT</a>
<!-- Desktop navigation — hidden on mobile via CSS -->
<ul class="nav-links desktop-nav">
<li><a href="index.html" aria-current="page">Home</a></li>
<li><a href="about.html">About</a></li>
<li><a href="services.html">Services</a></li>
<li><a href="contact.html">Contact</a></li>
</ul>
<!--
The hamburger label.
`for` must match the checkbox `id` exactly.
The three <span> elements become the animated bars.
`aria-label` gives screen readers a meaningful name.
`role="button"` and `tabindex="0"` ensure keyboard reachability.
-->
<label
for="menu-toggle"
class="menu-label"
aria-label="Toggle navigation menu"
role="button"
tabindex="0"
>
<span></span>
<span></span>
<span></span>
</label>
</div><!-- /.nav-inner -->
<!--
The sliding mobile menu panel.
It is a SIBLING of .nav-inner — this is what makes the CSS trick possible.
It is invisible by default (max-height: 0) and expands when
the checkbox above it is :checked.
-->
<nav class="mobile-menu" aria-label="Mobile navigation">
<ul>
<li><a href="index.html" aria-current="page">Home</a></li>
<li><a href="about.html">About</a></li>
<li><a href="services.html">Services</a></li>
<li><a href="contact.html">Contact</a></li>
</ul>
</nav>
</header>
CSS: Hiding the Checkbox, Styling the Hamburger
Copy the following CSS and paste it after the existing header styles in the css/styles.css.
/* ---------------------------------------------
Mobile Menu — The :checked Hack
-------------------------------------------- */
/* 1. Visually hide the checkbox but keep it in the accessibility tree */
.menu-checkbox {
position: absolute;
opacity: 0;
width: 0;
height: 0;
pointer-events: none;
}
/* 2. The nav bar row — flex container */
.nav-inner {
max-width: var(--max-width);
margin: auto;
padding: 1rem;
display: flex;
justify-content: space-between;
align-items: center;
}
/* 3. The hamburger label — three animated bars */
.menu-label {
display: none; /* Hidden on desktop */
flex-direction: column;
justify-content: center;
gap: 5px;
width: 2.25rem;
height: 2.25rem;
padding: 0.4rem;
cursor: pointer;
border-radius: 6px;
transition: background 0.2s;
}
.menu-label:hover {
background: var(--light-bg);
}
.menu-label:focus-visible {
outline: 2px solid var(--primary-color);
outline-offset: 2px;
}
.menu-label span {
display: block;
height: 2px;
background: var(--text-color);
border-radius: 2px;
transform-origin: center;
transition: transform 0.3s ease, opacity 0.3s ease, width 0.3s ease;
}
.menu-label span:nth-child(2) {
width: 75%; /* Offset middle bar for style */
}
/* 4. The sliding panel — closed by default */
.mobile-menu {
max-height: 0;
overflow: hidden;
border-top: 1px solid transparent;
transition:
max-height 0.4s cubic-bezier(0.4, 0, 0.2, 1),
border-color 0.4s;
background: var(--bg-color);
}
.mobile-menu ul {
list-style: none;
padding: 0.5rem 0 1rem;
max-width: var(--max-width);
margin: auto;
}
.mobile-menu li a {
display: block;
padding: 0.75rem 1rem;
font-weight: 500;
color: var(--text-color);
text-decoration: none;
border-radius: 6px;
transition: color 0.2s, background 0.2s;
}
.mobile-menu li a:hover {
color: var(--primary-color);
background: #eff6ff;
}
.mobile-menu li a[aria-current="page"] {
color: var(--primary-color);
font-weight: 700;
}
/*
THE MAGIC — :checked selectors
Note the ~ combinator: targets LATER siblings of the checkbox.
Structure required: checkbox → .nav-inner → .mobile-menu (all siblings)
*/
/* Open the panel when checked */
.menu-checkbox:checked ~ .mobile-menu {
max-height: 400px;
border-top-color: var(--border-color, #e5e7eb);
}
/* Animate bar 1: slide down and rotate to form top of X */
.menu-checkbox:checked ~ .nav-inner .menu-label span:nth-child(1) {
transform: translateY(7px) rotate(45deg);
}
/* Animate bar 2: fade and collapse */
.menu-checkbox:checked ~ .nav-inner .menu-label span:nth-child(2) {
opacity: 0;
width: 0;
}
/* Animate bar 3: slide up and rotate to form bottom of X */
.menu-checkbox:checked ~ .nav-inner .menu-label span:nth-child(3) {
transform: translateY(-7px) rotate(-45deg);
}
/*
RESPONSIVE — Switch between desktop and mobile nav
*/
/* On mobile: show hamburger, hide desktop nav */
@media (max-width: 640px) {
.desktop-nav { display: none; }
.menu-label { display: flex; }
}
/* On desktop: hide hamburger and mobile panel entirely */
@media (min-width: 641px) {
.menu-label { display: none; }
.mobile-menu { display: none; }
}
The checkbox hack cannot detect a click outside the menu — that requires JavaScript. For a business site, this is usually acceptable: the user clicks the × to close, or navigates to a page (which reloads the HTML, resetting the checkbox). If you need outside-click-to-close, that is the right time to reach for a small, targeted JavaScript snippet — but for most cases, this pure CSS approach is perfectly usable.
Conclusion
You’ve Built a Real Business Website.
What started as a single homepage is now a fully connected, professionally structured multi-page website — built entirely with HTML and CSS, no frameworks, no shortcuts.
This part was the most demanding phase of this series. You didn’t just add pages — you made architectural decisions that real professional developers make every day: how to structure files so paths never break, how to communicate page identity accessibly without JavaScript, how to make a form useful without a backend, and how to use CSS features that most developers reach for a framework to handle.
The techniques covered in Part 2 are not cosmetic additions — they are foundational, transferable skills. Every one of them applies directly to real client work, professional codebases, and the modern web platform.
Every experienced developer who reaches for a framework does so consciously, knowing exactly what problem that framework solves and what it costs in complexity, performance, and dependency maintenance. You now have the foundation to make that choice knowingly — not out of necessity.
Part 2 gave your website its complete structure and content. Part 3 will take it from complete to exceptional — focusing on the details that separate a student project from a site you’d confidently hand to a real client or launch under your own name.
To follow through, subscribe, like, comment, and share this tutorial.

