Build a mobile-first, collapsible navbar

Last updated: October 25, 2021.

In this tutorial, we will cover how to make a responsive navigation bar using HTML, CSS and vanilla JavaScript. Bootstrap offers a popular ‘out of the box’ navbar solution, but this involves importing a lot of CSS that could conflict with your own project’s styling. With a custom navbar, you have full control over styling and behaviour.

What we will build

Below is a live preview of what we will build in this tutorial:

See the Pen Untitled by James (@openjavascriptadmin) on CodePen.

Step 1: Add font awesome CDN

This tutorial uses font awesome icons for the navbar logo and toggle button. Make sure to include this link in the head of your HTML for these to be visible:

<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.14.0/css/all.min.css"/> 

Step 2: HTML markup

Inside the HTML bogs tags, we wrap the entire content of our navbar inside a nav element.

The first child inside of the container is a div with the id of nav-container. This contains all elements that will be shown by default on a small screen. It contains two children: the website logo and a button to toggle navigation (we will program this functionality later).

The second child of the nav-container is a ul element containing each of the main menu items as li elements.

The third child of nav-container is the social media icons also as a ul element with nested li elements for each icon.

As we take a mobile-first approach, we want to hide the main menu items and social media icons by default as these should not appear in a small-screen view. For this, we need to add some CSS.

  <nav id="nav-container">
    <!-- wrapper for small screen elements -->
    <div id="small-sreen-elements">
      <i id="logo" class="fas fa-terminal"></i>
      <button id="toggle-nav-button">
        <i class="fas fa-bars"></i>
      </button>
    </div>

        <!--
            NAVIGATION MENU ITEMS are, by using CSS media 
            queries, not visible on a small screen by 
            default and visible on a large screen. 
            
            On a small screen, they are made visible when a user 
            clicks the toggle navigation button by JavaScript adding
            a CSS class to the menu items making them visible
        -->

    <!-- Main menu items -->
    <ul class="menu-items">
      <li>
        <a href="#">Home</a>
      </li>
      <li>
        <a href="#">About</a>
      </li>
      <li>
        <a href="#">Projects</a>
      </li>
      <li>
        <a href="#">Contact</a>
      </li>
    </ul>
    
    <!-- Social media items -->
    <ul class="social-icons">
      <li>
        <a href="#"><i class="fab fa-facebook"></i></a>
      </li>
      <li>
        <a href="#"><i class="fab fa-twitter"></i></a>
      </li>
      <li>
          <a href="#"><i class="fab fa-instagram"></i></a>
      </li>
      <li>
        <a href="#"><i class="fab fa-linkedin"></i></a>
      </li>
    </ul>
  </nav>

Step 3: Adding mobile-first CSS

As we take a mobile-first approach, in our CSS we specify how we want our navbar to appear on a small screen before using CSS media queries to make edits to the styling for a larger screen.

For a smaller screen we add the following.

Note that we apply flexbox to the small-screen-elements div with justify-content set to space-between so that the logo and toggle navigation button are pushed to opposite ends of the navbar.

And importantly, both the main menu items and social media icons (the second and third children within the nav container) are hidden from view so that only the small-screen-elements are visible.

But we do not set the main menu items ul element to display: none. Instead, we set height to 0px and overflow to hidden. To see its contents, all we then need to do is give it a height. We will do this dynamically by adding a CSS class to give the main menu items a height when the user clicks the toggle navigation button.

/* Style the nav (always the same) */
nav {
    background-color: #d9138a;
}
nav ul {
    list-style-type: none;
}
nav a {
    font-family:sans-serif;
    text-decoration: none;
    color: whitesmoke
}

/* Style navbar for small screen */
#small-sreen-elements {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 1rem;
}
#toggle-nav-button {
  font-size: 1.5rem;
  margin-top: 0.4rem;
  color: white;
  background-color: transparent;
  border-color: transparent;
  cursor: pointer;
}
#logo {
  font-size: 2rem;
  color: rgb(30,30,30);
  padding-top: 0.8rem;
  padding-left: 1rem;
}


/* Style menu items */
/* Default class (no display) */
.menu-items {
  height: 0; 
  overflow: hidden;
  background-color: #322e2f;
}
.menu-items a {
display: block;
padding-top: 2rem;
font-size: 1rem;
margin-left: 1rem;
margin-right: 1rem;
}
/* No social icons */
.social-icons { 
    display: none;
}

The CSS styling we will add to the main menu items ul element is the following:

.show-menu-items {
  height: 15rem;
}

Add this to the CSS above. We are now ready to add JavaScript to get the toggle button working.

Step 4: Add JavaScript

First, we select the toggle navigation button and the menu items ul and store both in variables.

Next we add an event listener to the navigation toggle button that listens for a user click. When this is clicked, the toggleNav functions fires. This toggles the show-menu-items class element on the class list of the menu ul (toggle adds it to the class list if not present and removes it if present).

// Get toggle button and menu items ul
const toggleBtn = document.getElementById('toggle-nav-button');
const menuItems = document.querySelector('.menu-items');

// When toggle button pressed, run toggleNav function
toggleBtn.addEventListener('click', toggleNav);

function toggleNav() {
    menuItems.classList.toggle('show-menu-items');
}

Clicking on the navigation toggle button now toggles the menu!

All we need to do now is add a media query to our CSS so that our menu is also appropriate for larger screens.

Step 5: Adding a media query for larger screens

To make our navigation display well on large screens, we add a media query. We specify a min-width of 700px. For screens larger than this, we want a different layout.

Now, display: flex and justify-content: space-between is applied to the navbar wrapper so its three children are pushed to the far-left, middle and far-right of the container.

For a larger screen size, we make the menu items and social media icons visible by applying display: flex and the height of the menu items to auto (by default 0).

@media screen and (min-width: 700px) {
  /* Now apply flex to entire container */
  #nav-container { 
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 1rem;
  }
  /* Hide toggle button */
  #toggle-nav-button {
    display: none;
  }
   /* Flex for menu items  */
  .menu-items {
    height: auto;
    display: flex;
    flex-direction: row;
    background-color: #d9138a;
  }
   /* Remove padding applied in vertical view*/
  .menu-items li a { 
    padding-top: 0rem;
  }
   /* Social icons now visible */
  .social-icons {
    display: flex;
    flex-direction: row;
  }
  .social-icons a {
    margin-right: 0.8rem;
  }
  #logo {
    padding: 0;
}

Your navigation bar should now adapt to larger screens from a logo and toggle navigation button to logo, main menu items and social media icons.

Summary

In this tutorial we have seen how to code a mobile-first collapsible navbar using HTML, CSS and JavaScript.

Liked this project? Head to our project section for more HTML, CSS and JavaScript projects of varying difficulty.