Build a responsive navigation that remains usable
This project combines flex layout, a narrow-screen drawer, visible focus and a small amount of JavaScript for disclosure state. The links remain visible if JavaScript is unavailable. Use real links and a real button; CSS alone should not impersonate an interactive menu control.
HTML
Learning dashboard
CSS
*, *::before, *::after { box-sizing: border-box; }
.tech-lesson-content { margin: 0; font: 1rem/1.5 system-ui, sans-serif; }
.site-header { background: #172554; color: white; }
.site-nav {
position: relative; display: flex; align-items: center;
flex-wrap: wrap; gap: 1rem; max-width: 70rem;
margin-inline: auto; padding: 1rem;
}
.brand { font-weight: 700; margin-right: auto; }
.site-nav a { color: white; text-underline-offset: 0.25em; }
.nav-links { display: flex; gap: 1rem; list-style: none; margin: 0; padding: 0; }
.menu-toggle { display: none; padding: 0.6rem 1rem; font: inherit; }
a:focus-visible, button:focus-visible {
outline: 3px solid #fbbf24; outline-offset: 4px;
}
main { max-width: 70rem; margin-inline: auto; padding: 1rem; }
@media (max-width: 44rem) {
.nav-links { flex-basis: 100%; flex-direction: column; }
.js .menu-toggle { display: inline-block; }
.js .nav-links {
display: none; position: absolute; right: 0; top: 100%;
width: min(20rem, 100vw); padding: 1.25rem;
background: #172554; box-shadow: 0 8px 18px rgb(0 0 0 / 0.2);
z-index: 20;
}
.js .nav-links.is-open { display: flex; }
.nav-links a { display: block; padding: 0.5rem; }
}JavaScript, placed after the navigation
const toggle = document.querySelector('.menu-toggle');
const links = document.querySelector('#primary-links');
if (toggle && links) {
document.documentElement.classList.add('js');
toggle.hidden = false;
function closeMenu() {
links.classList.remove('is-open');
toggle.setAttribute('aria-expanded', 'false');
}
toggle.addEventListener('click', () => {
const opened = links.classList.toggle('is-open');
toggle.setAttribute('aria-expanded', String(opened));
});
document.addEventListener('keydown', event => {
if (event.key === 'Escape' && links.classList.contains('is-open')) {
closeMenu();
toggle.focus();
}
});
links.addEventListener('click', event => {
if (event.target.closest('a')) closeMenu();
});
}Expected behavior
On a wide viewport, the links appear in a row and the menu button is hidden. At a narrow width, the button opens a right-aligned dropdown drawer. This is a non-modal disclosure, so it does not lock page scrolling or trap focus. Escape closes an open drawer and restores focus to its button.
Verification checklist
- Tab to Menu, activate it with Enter or Space, and reach each link with Tab.
- Press Escape while open; verify the state and focus.
- Disable JavaScript: links should remain visible and usable in normal flow.
- Test 320px width, long labels and 200% zoom.
- Replace the example href values with real destinations and confirm there are no dead links.
Extensions
Add an active-page aria-current="page" marker. If the design later becomes a modal full-screen drawer, implement its different focus-management and dismissal requirements explicitly rather than merely adding a dark backdrop.