Creating a CSS Only Mobile Responsive Menu

A responsive mobile menu is a CSS technique that utilizes the CSS media query to alter the appearance of a menu when viewed on different devices and screen sizes. This technique lets you display a full menu when viewed on a large screen, but hide it and display a hamburger button when the screen size is reduced. When a user clicks the hamburger button, the menu will reappear at the top of the page.

In this article, I’ll guide you through the process of creating a responsive mobile menu without using any CSS framework or JavaScript code.

Markup

From a visual point of view, it may seem that the HTML structure is composed of menu items and the hamburger menu only. However, it will require some additional markup to make it work.

Let’s start with the menu items, which will be made of simple anchor tags placed inside the nav element:

<nav>
    <a href="">Home</a>
    <a href="">Blog</a>
    <a href="">About</a>
</nav>

To comply with the semantic HTML rules, wrap the nav with the header element:

<header>
    <nav>
        <a href="">Home</a>
        <a href="">Blog</a>
        <a href="">About</a>
    </nav>
</header>

Next comes the hamburger button. It is made up of two elements-a label and a checkbox. The label acts as a visual representation for the hamburger menu, while the checkbox element is used to store the button state indicating whether the menu is open of closed.

Also, you might have noticed that there is an empty span element inside the label. The span is used to display three vertical lines that make up the hamburger icon. You can replace the span with an SVG icon or an image element if you want.

<header>
    <input type="checkbox" id="menu-checkbox">
    <label class="menu-toggle" for="menu-toggle"><span></span></label>
    
    <nav>
        <a href="">Home</a>
        <a href="">Blog</a>
        <a href="">About</a>
    </nav>
</header>

No page header would be complete without a logo. So let’s add one and move to the stylesheet.

<header>
    <a href="">Logo</a>
    
    <input type="checkbox" id="menu-checkbox">
    <label for="menu-toggle"><span></span></label>

    <nav>
        <a href="">Home</a>
        <a href="">Blog</a>
        <a href="">About</a>
    </nav>
</header>

Style

Add some basic styling to the header and the logo.

header {
    display: flex;
    align-items: center;
    height: 2rem;
}

/* push everything next to the logo to the right */
.logo {
    margin-right: auto;
}

/* hide the the checkbox element */
#menu-checkbox {
    display: none;
}

We will use a mobile-first approach when defining the style for the menu. Mobile-first approach means that you start with writing styles for a small screen and then move on to a larger screen.

In this particular case, it means that the nav element should be hidden and the hamburger button should remain visible until the page width reaches a certain value.

/* initially, the nav element is not visible */
nav {
    display: none;
    
    /* other styles to layout children elements */
    align-items: center;
    justify-content: space-between;
}

/* make the nav visible when the page width goes beyond 500 px */
@media screen and (min-width: 500px) {
    nav {
        display: flex;
    }
}

The hamburger icon

Do you remember about the empty span element inside the label tag? The label is our hamburger button, and we will use the span element to turn it into a line.

However, to draw the ☰ icon, you need three lines, so how do you achieve this with a single span element? Thanks to ::before and ::after pseudo elements. Pseudo-elements allow you to insert a new element before or after the element they are applied to. Meaning that you can make two additional elements out of a single element defined in the markup.

/* first start with defining basic styles for the hamburger button */
.menu-toggle {
    display: flex;
    width: 24px;
    height: 24px;
    position: relative;
    cursor: pointer;
    align-items: center;
    z-index: 2;
}

/* once the page width reaches 500 px, hide the hamburger button */
@media screen and (min-width: 500px) {
    .menu-toggle {
        display: none;
    }
}

/* then comes the middle line of the hamburger icon, 
which is made by setting the height and background color of the element */
.menu-toggle span {
    background-color: black;
    display: block;
    height: 2px;
    width: 100%;
}

/* some common styles for the both of the pseudo-elements */
.menu-toggle span:after,
.menu-toggle span:before {
    background-color: black;
    content: '';
    display: block;
    position: absolute;
    height: 2px;
    width: 100%;
}

/* move the first line to the bottom */
.menu-toggle span:after {
    bottom: 0;
}

/* and the second one up */
.menu-toggle span:before {
    top: 0;
}

Button states

The last step is to make the button to show the menu when clicked. The first thing that comes to mind is probably to use JavaScript, but you can achieve that with CSS only.

An HTML checkbox element has two states—checked and unchecked. Both states can be targeted using the :checked or :not(checked) pseudo-class selectors.

When combined with the general sibling selector, we can make a sibling element appear when the checkbox is checked, and then go away when the checkbox is unchecked.

#menu-checkbox:checked ~ nav {
    /* make the menu visible  */
    display: flex;
    
    /* change the menu layout from horizontal to vertical */
    flex-direction: column;
    justify-content: center;
    align-items: center;
    
    /* make the menu stick to the top of the page */
    position: fixed;
    top: 0;
    right: 0;
    left: 0;
    padding: 1rem;
    
    background-color: darkgray;
}

/* add vertical spacing to elements inside the nav element */
#menu-checkbox:checked ~ nav a {
    margin: 2px 0;
}

/* if the menu is open and the page size is bigger than 500 px, keep the close button visible */
@media screen and (min-width: 500px) {
    #menu-checkbox:checked ~ .menu-toggle {
        display: flex;
    }
}

At this point, you should already have a working menu. However, there is a simple improvement that could improve the user experience. When you click the hamburger button and the menu is shown, the menu icon stays the same. It would be better to change the icon to something like X to indicate that clicking the button again will close the menu.

We can use a CSS transformation to turn the hamburger button into a close button when the menu is open. First, we need to hide the center line of the hamburger button. Then vertically align the remaining lines to the center, and rotate them into opposite directions by 45 degrees.

/* make the midline invisible */
#menu-checkbox:checked ~ .menu-toggle span {
    background-color: transparent;
}

/* move the upper line to the center and rotate it by 45 degrees clockwise */
#menu-checkbox:checked ~ .menu-toggle span:before {
    transform: rotate(45deg);
    top: auto;
}

/* move the bottom line to the center and rotate it by 45 degrees counterclockwise */
#menu-checkbox:checked ~ .menu-toggle span:after {
    transform: rotate(-45deg);
    bottom: auto;
}

Conclusion

When you create a website, you need to ensure that it’s easy to navigate on any device—whether it’s a small mobile device or a large desktop computer.

This article has shown you how to create a responsive mobile menu that adapts and looks great on any screen size.

You can find the complete code from this article here.