A navigation menu that is clear, concise, and easy to intuitively navigate is essential for an optimized website user experience. The responsiveness of the menu is a key factor as well. As of this writing, more than 54 percent of web traffic worldwide is attributed to mobile. With mobile-first responsive design, developers start with the smallest screen size and then gradually scale up, adding add more features and functionality for larger screen sizes. The resulting webpages will automatically adjust to the size of the user’s browser window.
However, as important as a responsive menu is to a website’s UX, it’s not necessary to build it in JavaScript. This tutorial will review how to create a mobile-first responsive menu using only HTML and CSS.
There are many techniques available for building responsive mobile menus. One common practice is to use pure CSS without one single line of JavaScript. This technique involves employing a simple HTML list structure to develop a menu of links that can be styled and rendered differently based on a device’s screen size.
In this tutorial, we’ll use CSS to build the below responsive menu for mobile, tablet, and desktop:
For smaller screens, the user must click on a hamburger icon to reveal the menu items. Larger screens will display the menu items inline in the navigation bar.
In this tutorial, we’ll build a responsive menu, including a hamburger icon, entirely from pure HTML and CSS.

Let’s try it out!
Table of Contents
Getting started
Using your favorite text editor, such as VS Code, create two files in one common folder:
index.html
for HTML codestyle.css
for CSS code
Copy the index.html
file path and paste it into a browser to preview the app.
Adding the HTML
Add the following code to the index.html
file:
<html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <!-- App title --> <title>Responsive Pure CSS Menu</title> <!-- Link CSS file --> <link rel="stylesheet" href="https://blog.logrocket.com/create-responsive-mobile-menu-with-css-no-javascript/style.css"> </head> <body> <!-- Navigation bar --> <header class="header"> <!-- Logo --> <a href="#" class="logo">LR</a> <!-- Hamburger icon --> <input class="side-menu" type="checkbox" id="side-menu"/> <label class="hamb" for="side-menu"><span class="hamb-line"></span></label> <!-- Menu --> <nav class="nav"> <ul class="menu"> <li><a href="#">Gallery</a></li> <li><a href="#">Blog</a> </li> <li><a href="#">About</a></li> </ul> </nav> </header> <!-- Main content --> <main> <article> <h1> Some content </h1> <p> More Content </p> </article> </main> </body> </html>
This code contains the structure and content of the web page. It also includes a reference to the CSS style sheet.
We use the header
and main
semantic tags to separate the navigation bar and the main content of the page.
We add a logo using the <a>
anchor tag.
Lastly, we create a hamburger menu using a checkbox
hack. With this strategy, we can style the menu according to whether the checkbox is checked.
We use a label
tag to define the hamburger menu icon. The input
tag is used to conditionally display the menu depending on the state of the checkbox (class side-menu
).
Then, we add the menu items as link list elements, <li>
, in an unordered list, ul
. The nav
tag serves as the list container.
Here’s the output with HTML only:
Adding the CSS
Now, we’ll use CSS to style the different UI components and features:
Styling the content and background
We’ll add the following code to the style.css
file to adjust the appearance of the HTML content:
/* Theming */ @import url("https://fonts.googleapis.com/css2?family=Poppins:[email protected];700&display=swap"); /* import font */ :root{ --white: #f9f9f9; --black: #36383F; --gray: #85888C; } /* variables*/ /* Reset */ *{ margin: 0; padding: 0; box-sizing: border-box; } body{ background-color: var(--white); font-family: "Poppins", sans-serif; } a{ text-decoration: none; } ul{ list-style: none; }
This code imports the Poppins Google font for use in the app.
We define the CSS variables for the colors to be used in the app. Then, we use CSS reset to remove the browser’s default settings for margin
, padding
, box-sizing
, text-decoration
, and list-style
.
We also specify a white background-color
and the Poppins font-family
for the page content.
Here’s the output, displaying the styled content and background:
Styling the header and logo
/* Header */ .header{ background-color: var(--black); box-shadow: 1px 1px 5px 0px var(--gray); position: sticky; top: 0; width: 100%; } /* Logo */ .logo{ display: inline-block; color: var(--white); font-size: 60px; margin-left: 10px; }
This code adds a black background-color
and gray box-shadow
to the header. To keep the header at the top of the screen during scrolling, we specify a sticky position
and a zero offset from the top
. We also adjust the header to stretch across the full width
of the device.
We style the logo by specifying the color
, font-size
, and left-margin
. Not to be confused with padding, the [margin]
is the area around the logo that separates it from other elements.
Here’s the output, displaying the styled header and logo:
/* Nav menu */ .nav{ width: 100%; height: 100%; position: fixed; background-color: var(--black); overflow: hidden; } .menu a{ display: block; padding: 30px; color: var(--white); } .menu a:hover{ background-color: var(--gray); } .nav{ max-height: 0; transition: max-height .5s ease-out; }
In this code, we specify width
and height
properties of 100 percent for the nav
element in order to fit the content to the screen. Then, we specify a fixed position
to overlay the navigation menu on top of the main app content. We also select a black background-color
for the nav
element and specify that any overflow
content from the nav
element should be hidden.
For the menu link elements, we specify a block
format display, add padding
and color
, and change the background-color
from white to gray on hover.
Lastly, we use the CSS [transition]
property and a max-height
of zero to hide the nav
element by default but reveal it when the menu icon is clicked.
Here’s the output, displaying the styled navigation menu:
/* Menu Icon */ .hamb{ cursor: pointer; float: right; padding: 40px 20px; }/* Style label tag */ .hamb-line { background: var(--white); display: block; height: 2px; position: relative; width: 24px; } /* Style span tag */ .hamb-line::before, .hamb-line::after{ background: var(--white); content: ''; display: block; height: 100%; position: absolute; transition: all .2s ease-out; width: 100%; } .hamb-line::before{ top: 5px; } .hamb-line::after{ top: -5px; } .side-menu { display: none; } /* Hide checkbox */
In this code, we specify that a pointer cursor
should be displayed when a user interacts with the hamburger menu. We position the label
element to the right and add some padding.
Next, we style the span
element to create the three menu icon lines.
We use the CSS pseudo-elements [::before]
and [::after]
on the span
element to define the three hamburger icon lines. The .hamb-line
selector defines the center (or second) line. The .hamb-line::before
and .hamb-line::after
position the first and third lines 5px
above and below the center line, respectively.
Lastly, we use the display
property to hide the checkbox (.side-menu
).
Here’s the output, displaying the styled hamburger menu:
/* Toggle menu icon */ .side-menu:checked ~ nav{ max-height: 100%; } .side-menu:checked ~ .hamb .hamb-line { background: transparent; } .side-menu:checked ~ .hamb .hamb-line::before { transform: rotate(-45deg); top:0; } .side-menu:checked ~ .hamb .hamb-line::after { transform: rotate(45deg); top:0; }
We style the hamburger menu icon to alter its appearance when checked. First, we specify the max-height
of the nav
element when the checkbox is checked (class .side-menu``:checked
).
Then, we follow a two-step process to create an “x”-shaped close icon to indicate that the checkbox is checked. First, we hide the second line of the hamburger icon by setting its background
to transparent
. Then, we rotate the first and third lines by -45 and 45 degrees, respectively, to form an “x” shape.
Here’s the output, displaying the toggled menu:
Adding responsiveness
We can make the app responsive by using media queries to include CSS properties conditionally. In other words, the properties inside a media query will be applied to the web page only when the condition set is valid.
/* Responsiveness */ @media (min-width: 768px) { .nav{ max-height: none; top: 0; position: relative; float: right; width: fit-content; } .menu li{ float: left; } .menu a:hover{ background-color: transparent; color: var(--gray); } .hamb{ display: none; } }
In this code, we add a @media
rule with the device condition set to a 768px min-width
. We want devices with this minimum width to see the full navigation menu, rather than the hamburger menu.
We remove the max-height
property of the nav
element by setting it to none
.
We position the nav
element at the top-right of the screen and specify its width to fit-content
.
We float
the menu list items to the left of the nav
. We specify the background color to be transparent and the menu list items to be gray on hover.
Lastly, we use the display
property to hide the hamburger menu icon.
Here’s the fully styled app:
This video demonstrates the app’s responsive user interface:
Conclusion
In this tutorial, we designed and built a mobile-first responsive menu using only HTML and CSS, with no JavaScript. The complete code used in this article is available on GitHub.
The technique used in this article is just one of many methods that can be used to build a responsive mobile menu. By experimenting with different methods, you can decide which ones you prefer. Happy coding!
Is your frontend hogging your users’ CPU?
As web frontends get increasingly complex, resource-greedy features demand more and more from the browser. If you’re interested in monitoring and tracking client-side CPU usage, memory usage, and more for all of your users in production, try LogRocket.https://logrocket.com/signup/
LogRocket is like a DVR for web and mobile apps, recording everything that happens in your web app or site. Instead of guessing why problems happen, you can aggregate and report on key frontend performance metrics, replay user sessions along with application state, log network requests, and automatically surface all errors.
Modernize how you debug web apps — Start monitoring for free.