Hamburger Menu

This is the first in a three page explanation of how to build a responsive navigation system which on mobiles is a hamburger with a dropdown navigation menu. On wider screens it is a horizontal flex panel showing a list of links and dropdowns. A mobile first approach is taken so on this page we start with the hamburger menu.

Our navigation consists of a top level list in which the list items are either links or a key word with a second level list of links. I think it is confusing for the user if they can be both a link and a dropdown so I have avoided that.

In the third explanation for combining the hamburger and flex menus we will see that the code for mobile is written first and the code for the wider width screens written second. This code is additional to the code for the smaller width size and some of the initial code will need to be reset to default values or to values necessary for the new screen size. This can be complex and it may be more straightforward to simply write a different set of code for each screen size.

The HTML

Here is the HTML code for the navigation. This does not change throughout this three page demonstration except for the container element which starts as <div id="nav-0"> and is incremented by 1 in each successive example as the CSS is built up. So, we get <div id="nav-0">, <div id="nav-1">, <div id="nav-2"> etc. This enables us to target essentially the same HTML with different CSS code as we develop the CSS from start to end product.

It may be useful to have the HTML code shown below open in a second browser window alongside this web page so it can be referred to as the CSS is built up. CSS code which is newly introduced to the demonstration is colored red. Here is the HTML:


      <div id="nav-0">
        <button id="hamburger-button">
          <span class="hamburger-bar"></span>
          <span class="hamburger-bar"></span>
          <span class="hamburger-bar"></span>
        </button>
        <ul class="top-level-list">
          <li>
            <span class="no-link" tabindex="0">INFO</span>
            <ul>
              <li><a href="#">SHOPS</a></li>
              <li><a href="#">TRAVEL</a></li>
            </ul>
          </li>
          <li><a href="#">LINE-UP</a></li>
          <li>
            <span class="no-link" tabindex="0">MORE</span>
            <ul>
              <li><a href="#">WORKSHOPS</a></li>
              <li><a href="#">CONTACT</a></li>
            </ul>
          </li>
        </ul>
      </div>
    

Here is what it looks like without any CSS applied:

Clicking on any of the links makes the page jump up to the top if has been scrolled down at all. More on that later.

Styling the Hamburger

The approach here is to use CSS to create a hamburger icon inside a button element. An image icon or a font version of a hamburger could have been used. As this is not the main purpose of this demonstration I will not explain this CSS. It has transformed the empty button icon from the previous example. Here it is:


        #nav-1 #hamburger-button {
          width: 3rem;
          background: transparent;
          position: relative;
          cursor: pointer;
          border: none;
        }
        #nav-1 .hamburger-bar {
          width: 100%;
          height: 0.2rem;
          background: black;
          display: block;
          margin: 0.4rem 0;
        }
      

Here is the result:

In future when the CSS is shown I will not show the code for styling the hamburger button.

display: none / block

We will hide the whole navigation list like this: #nav .top-level-list {display: none;}

To show the list we need to revert the display property of the top level navigation list to the default value of block. Note that ul elements are block level elements by default. We need to do this when the hamburger icon receives focus ie. is touched on a mobile or clicked with a mouse on a laptop or desktop.

We can achieve this (but don't for reasons that will be discussed) by using the :focus CSS pseudo class on the #hamburger-button as the first part of the selector and then the top-level-list as the second part of the selector. The CSS looks like this:


      #nav-2 .top-level-list {
        display: none;
      }
      #nav-2 #hamburger-button:focus + .top-level-list {
        display: block;
      }
    

Note that the CSS sibling selector symbol, + is used to select the sibling element with the class attribute value of top-level-list. Here is the result which can be clicked on to show the navigation list:

There are two issues here. One is that when the hamburger is clicked and the list become visible it causes content below it to reposition. This may or may not be wanted. If it is not wanted then the position property of the navigation list can be given a value of absolute. Then it will be taken out of the normal flow of the document and will sit on top of content in the normal flow. The list will then need a background so the underlying content does not make it difficult to read. We will do this later.

The other issue is more serious. If the hamburger is clicked so the list becomes visible and then a link in the list is clicked, then the list immediately disappears. This is because it is only the hamburger button that received focus and when you click on one of the list link items you are clicking outside the focussed element so it loses focus. Once the hamburger element loses focus the top level list's display property reverts to the value of none. This happens and the intended action, that the browser should load the page linked to by the list element that was clicked on, does not happen. Note here that in this demonstration we are using href="#" for the links which causes the current page to reload if the link works. If the page is scrolled down it will cause the page to jump to the top. This is not happening here when you click on one of the links for the reason outlined above. A solution to this is to use the :focus-within pseudo class instead of the :focus pseudo class. MDN has this to say about it:

The :focus-within CSS pseudo-class represents an element that has received focus or contains an element that has received focus. In other words, it represents an element that is itself matched by the :focus pseudo-class or has a descendant that is matched by :focus.

So we need to use :focus-within on a common parent of both the hamburger button and the top level navigation list. Now when any element, for example the hamburger button, within that common parent element is clicked, focus will be conferred to all the elements inside the parent container and this includes the top level navigation list. Now clicking on one of the links in the navigation list will not cause focus to be lost and the link will function as it should.

#nav:focus-within

Looking at the HTML tells us that the first element that is a parent of both the hamburger button and the navigation list is the div element with id="nav" (appended with -n in our examples in order that the CSS can distinguish between them). So, let us apply the :focus-within pseudo class to this div element and select its child element with the class name of top-level-list. Note that the + has now gone because we are selecting the top-level-list element that is a child of the focussed element not a sibling of it as before. The CSS now looks like this:


      #nav-3 .top-level-list {
        display: none;
      }
      #nav-3:focus-within .top-level-list {
        display: block;
      }
    

... and see what it does (if you click on one of the links you may need to scroll back down to this point):

Now when you click on a link in the navigation list the page should jump to the top because clicking on the link does not close the list and the intended action of the link can happen. When you scroll back down to the point where you clicked the list is still displayed. This is irrelevant to the user as normally they would have been taken to a new page specified in the link element.

Absolute Position

When the list appears it pushes content below it down which may be considered an unwanted distraction. In order to prevent this from happening the navigation list can be given a position property value of absolute. This takes it out of normal flow and it will sit on top of content in normal flow. The position it occupies will by default be related to the browser window. We want its position to be relative to the hamburger icon from which it appeared. An absolutely positioned element will be relative to the window or to a parent element that has a position property with a value of relative. We also set a background-color property on the navigation list so it is not made difficult to read by the content underneath it showing through. Thus our CSS code now looks like this:


      #nav-4 .top-level-list {
        display: none;
        position: absolute;
      }
      #nav-4:focus-within .top-level-list {
        display: block;
        background-color: lightgrey;
      }
      #nav-4 #hamburger-button {
        position: relative;
      }
      
    

Click the hamburger to see the result:

That is the end of the demonstration. Considerable more styling would need to be done to make the list more attractive. I have put some Lorem Ipsum text below because the hamburger dropdown list will extend below the bottom of the screen and be invisible if there is not sufficient content below it.

"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."

"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?"