DEV Community

Michael Di Prisco
Michael Di Prisco

Posted on • Edited on

Creating a lateral menu with CSS only

[TL;DR] Here's the demo: Codepen

Before you get onboard and read this post..

This is just an experiment. Javascript have become way too fast to consider a CSS-only lateral menu, but still it has been a nice experiment to understand how to build complex things with simple instruments on the web.

Another important thing to consider: this is my first post, so I'm sorry if it's not as good as it could have been with little more writing skills.

Let's go...

It started out as an experiment, but then became an obsession, so I hope to build a little series on it. Can we build (almost) everything in CSS-only? To be clear, I'm trying to build everything I can without JavaScript, but mantaining an acceptable browser-compatibility (I'm talking about 99% of the market, so Flexbox could be ok, Grid is not), so I can't take in consideration latest specs and browser-prefixed stuff. I'm also trying to build something simple enough to be read and understood by new front-end developers and first-time CSS guys.

How I Did It...

I started to approach :hover, :active, :focus selectors, but soon enough it became clear those options can't guarantee enough stability to proceed, so I had an idea: Checkboxes. I read about this technique somewhere on the web (Probably CSS-Tricks, here on Dev.to, StackOverflow or some other related website), but all those examples didn't cover some edge cases and, as I stated earlier, were just too complex to be read from someone who hasn't been in this field long enough.
So I took that road and started writing something and, after some failed tries and bad ideas.. it became something pretty good (Or good enough for me, at least).

The HTML...

For the sake of simplicity, I didn't work much on UI, colors and dimensions. Just some simple markup is enough to explain what is happening, so here you have it:

<!DOCTYPE html>
<html>
  <body>
    <!-- Our Hamburger -->
    <label id="hamburger" for="main-menu">
      <span></span>
      <span></span>
      <span></span>
    </label>
    <!-- Just put some page markup to make clear we can separate label from checkbox without any issue -->
    Lorem Ipsum Dolor Sit Amet Consectetur.
    <!-- Our Lateral Menu Trigger -->
    <input type="checkbox" id="main-menu">
    <!-- Our Lateral Menu -->
    <div>
      <ul>
        <li>Lorem</li>
        <li>Ipsum</li>
        <li>Dolor</li>
        <li>Sit</li>
        <li>Amet</li>
      </ul>
    </div>
    <!-- This will be the page overlay used to close the menu -->
    <label for="main-menu"></label>


  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

The CSS...

CSS, in fact, is even simplier because we can just use some CSS adjacent selectors ("+") to properly format our main menu and make it work:


/* Some simple (And surely non-optimal) reset */
html, body{
  margin: 0;
  width: 100vw;
  height: 100vh;
  overflow: hidden;
}

body{
  padding: 8px;
}

/* Here's our hamburger */
label#hamburger{
  margin: 16px;
  display: block;
  background-color: #DEDEDE;
  width: 48px;
  height: 48px;
  position: relative;
  cursor: pointer;
  border-radius: 50%;
}

/* And his little children */
label > span {
  position: absolute;
  left: 12px;
  width: 24px;
  height: 4px;
  background-color: #FFFFFF;
}

label > span:first-child{
  top: 14px;
}

label > span:nth-child(2){
  top: 22px;
}

label > span:last-child{
  top: 30px;
}

/* Here's our invisible checkbox */
input[type="checkbox"]{
  margin: -1px;
  padding: 0;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip: rect(0, 0, 0, 0);
  position: absolute;
}

/* We put our adjacent div - the main menu - left enough to make it invisible */
input[type="checkbox"] + div{
  position: fixed;
  width: 10vw;
  height: 100vh;
  transition: left 0.5s ease;
  top: 0;
  left: -10vw;
  z-index: 10;
  background-color: #DEDEDE;
}

/* And there we have our overlay, which will stay right below the main menu (Z-index lower than main-menu's), allowing us to close it with a click. In fact, we are using the same trick as we did earlier to open the menu */
input[type="checkbox"] + div + label{
  position: fixed;
  width: 100vw;
  height: 100vh;
  top: 0;
  left: -100vw;
  cursor: pointer;
  z-index: 9;
  transition: left 0.3s ease;
  background-color: black;
  opacity: 0.2;
}

/* Aaaaand.. Here we show both the main menu and the overlay */
input[type="checkbox"]:checked + div, input[type="checkbox"]:checked + div + label{
  left: 0;
}
Enter fullscreen mode Exit fullscreen mode

So there you have it.. By clicking on the hamburger label, we in fact check our trigger checkbox and, consequently, allow our lateral menu and his adjacent overlay to drop into the view with little effort. In fact, we can even re-arrange our code to make our menu droppable from every side (What about some classes?).

Level it up a bit..

I guess we can all agree that not every menu comes from left, so how can we level things up a bit and allow our menu to be re-usable?

We could, simply enough, add some classes to our checkbox to make our menu drop from wherever we want.
First thing first: We have to remove every instance of left, top and transition properties from our main menu and label and add some CSS to properly render every situation:

input[type="checkbox"] + div{
  position: fixed;
  width: 10vw;
  height: 100vh;
  z-index: 10;
  background-color: #DEDEDE;
}

input[type="checkbox"] + div + label{
  position: fixed;
  width: 100vw;
  height: 100vh;
  cursor: pointer;
  z-index: 9;
  background-color: black;
  opacity: 0.2;
}
Enter fullscreen mode Exit fullscreen mode

Then we can work on our 4 cases: Left, Right, Top, Bottom.

/* From Left */
input[type="checkbox"].from-left + div, input[type="checkbox"].from-left + div + label{
  transition: left 0.5s ease;
  top: 0;
}

input[type="checkbox"].from-left + div{
  left: -10vw;
}

input[type="checkbox"].from-left + div + label{
  left: -100vw;
}

input[type="checkbox"].from-left:checked + div, input[type="checkbox"].from-left:checked + div + label{
  left: 0;
}

/* From Right */
input[type="checkbox"].from-right + div, input[type="checkbox"].from-right + div + label{
  transition: right 0.5s ease;
  top: 0;
}

input[type="checkbox"].from-right + div{
  right: -10vw;
}

input[type="checkbox"].from-right + div + label{
  right: -100vw;
}

input[type="checkbox"].from-right:checked + div, input[type="checkbox"].from-right:checked + div + label{
  right: 0;
}

/* From Top */
input[type="checkbox"].from-top + div, input[type="checkbox"].from-top + div + label{
  transition: top 0.5s ease;
  left: 0;
}

input[type="checkbox"].from-top + div{
  top: -100vh;
}

input[type="checkbox"].from-top + div + label{
  top: -100vw;
}

input[type="checkbox"].from-top:checked + div, input[type="checkbox"].from-top:checked + div + label{
  top: 0;
}

/* From Bottom */
input[type="checkbox"].from-bottom + div, input[type="checkbox"].from-bottom + div + label{
  transition: bottom 0.5s ease;
  left: 0;
}

input[type="checkbox"].from-bottom + div{
  bottom: -100vh;
}

input[type="checkbox"].from-bottom + div + label{
  bottom: -100vw;
}

input[type="checkbox"].from-bottom:checked + div, input[type="checkbox"].from-bottom:checked + div + label{
  bottom: 0;
}

Enter fullscreen mode Exit fullscreen mode

The JS...

Did you read the title or what? No JS!

Conclusion...

It was a nice experiment, and in fact I like experimenting and building CSS-only stuff. Of course, as I said earlier, a simple class toggle in JS could make our main menu's life way easier, but still I think it's a nice way to prove CSS can do many things we didn't initially think about.

I hope you liked my first post and hope to write many more in the future.

Top comments (0)