How to make a responsive navbar with CSS grid, HTML, and javascript | step-by-step guide

April 10, 2020

Hi, recently I rewrote the style for my website and it took me a while to figure out how to make a responsive navbar.

I did find some tutorials using float or flexbox or some weird positioning techniques that I don't really like. I ended up piecing together information from different places and building my navbar with CSS grid, which I think is easier to understand and makes the code cleaner. So I'm writing this post to show you how you can use CSS grid to make a responsive navbar. Enjoy.

Preview here: https://codepen.io/jamesku/pen/BaoyQed

Complete The HTML

Let's first complete the HTML part and then we will start styling it.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="style.css">
    <link href="//maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
    <link href="https://fonts.googleapis.com/css2?family=Mukta:wght@500&display=swap" rel="stylesheet">

    <title>Responsive Navbar</title>

</head>

<body>
    <nav class="navBar">
        <div class="brand">
            <a href="#">
                <h4>James Ku</h4>
            </a>
        </div>
        <ul class="navLinks">
            <li><a href="#">Home</a></li>
            <li><a href="#">About</a></li>
            <li><a href="#">Projects</a></li>
        </ul>
        <ul class="socialLinks">
            <li><a href="#"><i class="fa fa-facebook-official"></i></a></li>
            <li><a href="#"><i class="fa fa-github"></i></a></li>
        </ul>
        <div class="hamburger">
            <div></div>
            <div></div>
            <div></div>
        </div>
    </nav>
    <script>
        const hamburger = document.querySelector(".hamburger")
        const navLinks = document.querySelector(".navLinks")
        hamburger.addEventListener("click", () => {
            navLinks.classList.toggle("open")
        })
    </script>
</body>

</html>

Overwrite Default Settings

Just Changing the font text color in links.

* {
    margin: 0px;
    padding: 0px;
    box-sizing: border-box;
    font-family: 'Mukta', sans-serif;
}

a {
    text-decoration: none;
    color: black;
}

navbar layout

.navBar {
    background-color:#eee;
    display: grid;
    grid-template-columns: 1fr 3fr 1fr;
    justify-items: center;
    align-items: center;
}

Setting the background color here. You can set it to whatever color you like.

grid-template-columns: 1fr 3fr 1fr; means were are dividing the entire navbar into 3 columns, the width of them being 1:3:1. Play around with it to see how it works here:

https://codepen.io/jamesku/pen/ExVxOqW

justify-items means the content in each column will be centered(horizontally).

Similarly, align-items aligns items vertically;

Brand font size

Set the brand font size and position appropriately.

.brand {
    font-size: 2rem;
    margin: 0 2rem;
}

navLinks and socialLinks

Lets make the Links look a little bit better. Basically just making the list horizontal and adding some space between the items.

.navLinks {
    display: grid;
    grid-auto-flow: column;
    list-style: none;
    font-size: 1.3rem;
    column-gap: 5rem;
    justify-items: center;
}

.socialLinks {
    display: grid;
    grid-auto-flow: column;
    list-style: none;
    column-gap: 2rem;
    font-size: 1.6rem;
}

Make it Responsive

The navbar is already looking pretty good. Now we just have to make it responsive

First let's add a media query. This basically means everything inside this media query will only apply when the width of the window is less than 768px.

Note that everything we add from now on goes inside this query.

@media screen and (max-width: 768px) {
...
}

Hide the brand

We don't want to display the brand when the screen size is small so (note that this will remove the brand from the grid):

.brand {
    display: none;
}

hamburger

Now the hamburger is still an empty div. We want it to show in the mobile layout. So let's add (Notice we give hamburger a z-index of 3. I will explain this later):

.hamburger {
    cursor: pointer;
    justify-self: right;
    margin-right: 2rem;
    z-index: 3;
}
    
.hamburger div{
    width: 30px;
    height: 3px;
    background-color: black;
    margin: 5px 0;
}

Change the navbar grid layout

Since we only want the social links and the hamburger on the navbar, we need to change the layout to only 2 columns:

.navBar {
        grid-template-columns: 1fr 1fr;
}

Because we have only 2 columns but we have 3 items (navLinks, socialLinks and the hamburger), the hamburger now is automatically pushed to the second row. So we are going to "detach" navLinks from the navbar by changing the position property.

.navLinks {
    position: fixed;
}

If you are following along, you will notice navLinks does not take up a column anymore, because by changing position to fixed, navLinks is no longer under the effect the the grid layout.

Now let's change the height and the width so navLinks will cover the entire screen. Since it does not have a background color yet, let's set it to the same color as the navbar, #eee;

.navLinks {
    position: fixed;
    width: 100%;
    height: 100vh;
    background-color: #eee;
    z-index: 2;
}

Now the entire page is color by the links! But because hamburger has a z-index of 3, we can still see it.(You can imagine it as the higher the z-index, the closer it will be to you, covering everything with a lower z-index)

Now we want to change it so the list is vertical.

.navLinks {
    position: fixed;
    width: 100%;
    height: 100vh;
    background-color: #eee;
    z-index: 2;
    /* new */
    grid-auto-flow: row;
    grid-auto-rows: 7rem;
    align-items: center;
}

With grid-auto-flow: row;, we make grid add a new row for every new item in the container instead of a new column, making the list vertical.

grid-auto-rows: 7rem; just changes how tall each row is to make the list prettier.

align-items: center; means each item will be centered vertically in each row.

Implement the toggle functionality

We are almost done! Now we just need to complete the toggle functionality. First let's add the javascript code

const hamburger = document.querySelector(".hamburger")
    const navLinks = document.querySelector(".navLinks")
    hamburger.addEventListener("click", () => {
        navLinks.classList.toggle("open")
})

What this does is it basically adds/remove a toggle class to navLinks whenever you click on the hamburger. You can do this in React as well, which is what I did. Just use state to store a variable that controls the toggle and change the className according to the state.

To make the Menu only show when the toggle is on i.e. navLinks has class open:

.navLinks {
    position: fixed;
    width: 100%;
    height: 100vh;
    background-color: #eee;
    z-index: 2;
    grid-auto-flow: row;
    grid-auto-rows: 7rem;
    align-items: center;
    /* new */
    clip-path: circle(0px at 100% 0%);
    -webkit-clip-path: circle(0px at 100% 0%);
    transition: all 0.5s ease-out;
}

.navLinks.open {
    clip-path: circle(1000px at 100% 0%);
    -webkit-clip-path: circle(1000px at 100% 0%);
}

The clip-path property basically defines a mask, only the area inside the mask will be visible. circle(1000px at 90% -10%) defines a circle with a center at 100% screen width from the left edge of the screen towards the right and 0 percent height from the top of your screen. So 100% 0% essential means the top right corner of the screen.

clip-path and -webkit-clip-path basically do the same thing, but we use both to make sure it works on different browsers.

When the class open is on, we change the radius of the circle to 1000px so the entire Menu will be cover by the circle, making the whole menu visible.

transition: all 0.5s ease-out; is responsible for the animation. This makes all the style changes to the navLinks run over a duration of 0.5 seconds.

That's pretty much it! Thanks for reading. Hope this helped.

Subscribe to my email list

© 2022 ALL RIGHTS RESERVED