Building a Responsive Navigation Menu with Vanilla JS

Published on March 7, 2025
CSS HTML JavaScript

In this tutorial, we'll build a responsive navigation menu using only vanilla JavaScript, HTML, and CSS. This is a common component on websites that adapts to different screen sizes without relying on any frameworks.

What We'll Create

We'll build a navigation menu that:

  • Displays horizontally on larger screens
  • Collapses into a hamburger menu on mobile devices
  • Has smooth transitions for opening and closing
  • Is fully accessible with keyboard navigation
  • Uses only vanilla JavaScript - no jQuery or other libraries

HTML Structure

Let's start with the HTML structure:

<header class="header">

<div class="logo"> <a href="/">SugarFreeCode</a> </div>

<button class="nav-toggle" aria-label="Toggle navigation" aria-expanded="false"> <span class="bar"></span> <span class="bar"></span> <span class="bar"></span> </button>

<nav class="nav" data-visible="false"> <ul class="nav-list"> <li><a href="/">Home</a></li> <li><a href="/tutorials">Tutorials</a></li> <li><a href="/about">About</a></li> <li><a href="/contact">Contact</a></li> </ul> </nav> </header>

Note the accessibility features we've included:

  • aria-label on the toggle button to describe its purpose
  • aria-expanded which will be updated with JavaScript to reflect the menu state
  • data-visible attribute on the nav element to track its visibility state

CSS Styling

Now let's add the CSS to style our navigation:

/ Basic reset /

  • {
margin: 0; padding: 0; box-sizing: border-box; }

body { font-family: sans-serif; font-size: 16px; line-height: 1.5; }

/ Header and navigation styles / .header { display: flex; justify-content: space-between; align-items: center; padding: 1rem 2rem; background-color: #2c3e50; color: white; }

.logo a { color: white; text-decoration: none; font-size: 1.5rem; font-weight: bold; }

.nav-list { display: flex; list-style: none; gap: 2rem; }

.nav-list li a { color: white; text-decoration: none; transition: color 0.3s ease; }

.nav-list li a:hover { color: #3498db; }

/ Hide the hamburger menu button on desktop / .nav-toggle { display: none; }

/ Media query for mobile devices / @media (max-width: 768px) { .nav { position: fixed; inset: 0 0 0 30%; background: hsla(200, 50%, 20%, 0.97); padding: min(30vh, 10rem) 2rem; transform: translateX(100%); transition: transform 350ms ease-out; z-index: 1000; }

.nav[data-visible="true"] { transform: translateX(0%); }

.nav-list { flex-direction: column; }

.nav-toggle { display: block; position: absolute; top: 2rem; right: 2rem; background: transparent; border: none; cursor: pointer; width: 2rem; aspect-ratio: 1; z-index: 9999; }

.bar { display: block; width: 100%; height: 3px; margin: 5px auto; background-color: white; transition: all 0.3s ease-in-out; }

/ Hamburger to X animation / .nav-toggle[aria-expanded="true"] .bar:nth-child(1) { transform: translateY(8px) rotate(45deg); }

.nav-toggle[aria-expanded="true"] .bar:nth-child(2) { opacity: 0; }

.nav-toggle[aria-expanded="true"] .bar:nth-child(3) { transform: translateY(-8px) rotate(-45deg); } }

JavaScript Functionality

Finally, let's implement the JavaScript to handle the mobile menu toggle:

document.addEventListener('DOMContentLoaded', () => {

const navToggle = document.querySelector('.nav-toggle'); const nav = document.querySelector('.nav');

// Toggle menu when hamburger button is clicked navToggle.addEventListener('click', () => { const visibility = nav.getAttribute('data-visible');

if (visibility === "false") { nav.setAttribute('data-visible', true); navToggle.setAttribute('aria-expanded', true); } else { nav.setAttribute('data-visible', false); navToggle.setAttribute('aria-expanded', false); } });

// Close menu when clicking outside document.addEventListener('click', (e) => { if ( nav.getAttribute('data-visible') === 'true' && !nav.contains(e.target) && !navToggle.contains(e.target) ) { nav.setAttribute('data-visible', false); navToggle.setAttribute('aria-expanded', false); } });

// Close menu when ESC key is pressed document.addEventListener('keydown', (e) => { if (e.key === 'Escape' && nav.getAttribute('data-visible') === 'true') { nav.setAttribute('data-visible', false); navToggle.setAttribute('aria-expanded', false); } }); });

Putting It All Together

Now we have a fully functional responsive navigation menu built with vanilla JavaScript, HTML, and CSS. The menu displays horizontally on desktop screens and collapses into a hamburger menu on mobile devices with smooth animations and transitions.

Accessibility Considerations

Our navigation menu includes several important accessibility features:

  • Proper ARIA attributes to indicate the state of the menu (expanded/collapsed)
  • Keyboard support (ESC key to close the menu)
  • Semantic HTML structure
  • Sufficient color contrast for readability

Browser Compatibility

This solution works in all modern browsers including:

  • Chrome
  • Firefox
  • Safari
  • Edge

For older browsers like IE11, you may need to provide some polyfills for modern JavaScript features or adjust the CSS to use more compatible properties.

Conclusion

You've learned how to create a responsive navigation menu using only vanilla JavaScript, HTML, and CSS. This approach gives you complete control over the functionality and styling without the overhead of frameworks or libraries.

By understanding the underlying techniques, you can customize this pattern for your own projects and create lightweight, performant navigation components without dependencies.