Easy micro-interactions in CSS (Pt1): Hamburger menu icons
Tags: css, microinteractions, ui, frontend
Published: 2019-11-11 14:44:38

Whether you’re a designer or a developer working on the user-facing side of things, you have probably noticed how important micro-interactions can be. Let’s think about what micro-interaction is — it is an interaction that revolves around a single moment or use-case, regardless of what is happening in the rest of the app. As such I believe it is important to look at them in a vacuum and see how they work and look by themselves.

This is the first of the series on micro-interactions, and it is going to be fully devoted to the hamburger menu icons, one of the most frequently used elements on the web. We’ll go through the 3 most popular types of motions and how to replicate them in CSS. (With some JS to only toggle a class).


##Stack and rotate

![Stack and rotate](https://thepracticaldev.s3.amazonaws.com/i/h2uzbm4x4x1fqa6hnxst.gif)

This is one of the most widely used interactions that you can see in many apps and sites, let’s break what happens down.

1. Stacking 3 bars on top of each other in the middle
2. Hiding one of the bars
3. Rotating the 2 visible bars to for an X
4. Reverse interaction on close

Let’s start by preparing our HTML, I’ll be using slim since it’s more concise, but of course, you can use either pure HTML or whatever preprocessor you prefer!

We’ll start by drawing the 3 bar elements inside a container. This is the base we will use for most examples:

```slim
body
  .hamburger
    .bar
    .bar
    .bar
```


Now, for the CSS, let's create the base by positioning our bars to look collapsed (default icon):

```scss
.hamburger {
  height: 2.3rem;
  width: 3rem;
  display: inline-block;
  cursor: pointer;
  position: relative;
}
.bar {
  border-bottom: 0.33rem solid white;
  position: absolute;
  display: block;
  width: 100%;
  &:first-of-type {
    top: 0;
  }
  &:nth-of-type(2) {
    top: 1rem;
  }
  &:nth-of-type(3) {
    top: 2rem;
  }
}
```


We'll implement the interaction by toggling a CSS class, here's a short JS that will handle that:

```javascript
const hamburger = document.querySelector('.hamburger');
hamburger.addEventListener('click', function(){
  hamburger.classList.toggle('opened');
});
```


Now that the base is ready, let's get straight to the animation, it consists of 2 parts: stacking and rotation. We'll avoid using keyframes and set transition property to trigger at various timings instead, you can pass comma-separated values as `type-of-transition duration delay`. We'll add this into `.opened .bar` like so:

```scss
transition:
  top 100ms,
  transform 100ms 230ms,
  opacity 100ms;
```

Time to apply styles to the `.opened` class, we're going to fade out the middle bar and move and rotate the top and bottom ones to shape a line first, and the X next. We're also going to apply a reverse transition timing for when the X shifts back into a stacked icon.

```scss
.opened {
  .bar {
    transition:
      top 100ms,
      transform 100ms 230ms,
      opacity 100ms;
    &:first-of-type {
      top: 1em;
      transform: rotate(45deg);
    }
    &:nth-of-type(2) {
      opacity: 0;
    }
    &:nth-of-type(3) {
      top: 1em;
      transform: rotate(-45deg);
    }
  }
}
```


To spice it up a little let’s also give our animation that little snap effect by adding transition-timing-function to both `.hamburger .bar` and `.opened .bar`

```scss
transition-timing-function: cubic-bezier(1, 0.05, 0.62, 1.78);
```

End result:
{% codepen https://codepen.io/saintasia/pen/XWWZjgz %}

##Slide and rotate

![Slide and rotate](https://thepracticaldev.s3.amazonaws.com/i/xi9nk0uo7jb9k6cpjw3x.gif)

This interaction is similar to that above, except one might call it more primitive (or efficient!), all animations can be done in 1 step. But let’s see what we should do overall.

1. Slide out the middle bar and decrease its opacity
2. Rotate the other 2 bars to shape an X

We’ll start with the base from our previous example and go straight to adding effects to bar elements.

The initial position of our bars is the same, what’s different is this time we will add `transition: all` to simplify our code since there’s no need for multi-step animation, like so

```scss
.hamburger {
  height: 2.3rem;
  width: 3rem;
  display: inline-block;
  margin: 0 auto;
  cursor: pointer;
  position: relative;
  
  .bar {
    border-bottom: 0.33rem solid white;
    position: absolute;
    display: block;
    width: 100%;
    transition: all 350ms;
    transition-timing-function: cubic-bezier(1, 0.05, 0.62, 1.78);
    &:first-of-type {
      top: 0;
    }
    &:nth-of-type(2) {
      top: 1rem;
    }
    &:nth-of-type(3) {
      top: 2rem;
    }
  }
}
```

We will also tweak our `opened` class, especially the second element which now needs to slide out to the left and to become transparent:

```scss
.opened {
  .bar {
    &:first-of-type {
      top: 1em;
      transform: rotate(45deg);
    }
    &:nth-of-type(2) {
      opacity: 0;
      transform: translateX(-30px);
    }
    &:nth-of-type(3) {
      top: 1em;
      transform: rotate(-45deg);
    }
  }
}
```

End result:

{% codepen https://codepen.io/saintasia/pen/XWWZMaz %}

##Flip

![Flip](https://thepracticaldev.s3.amazonaws.com/i/dikzfhqxl0izm5b59ssi.gif)

A great way to use pre-made custom icons, a flip hamburger button is another thing I see quite frequently. The mechanics are simple, one side had a hamburger icon and another side has a close icon, it flips on click (or on some action).

To make it happen, we first need to adjust our HTML (Slim)

```slim
body
  .hamburger
    .hamburger-button
      .icon-opened
        .bar
        .bar
        .bar
    .hamburger-button.hamburger-button-back
      .icon-closed
        .bar
        .bar
```

This should create two elements, one we’ll style to look like a bar icon, another one will be shaped like an x, with `.hamburger-button` working as a wrap for the background and inner `icon-` elements containing our bars. And yes, that is quite a bit of code, but only because we’re creating our icons in CSS, if you use an SVG icon, this should look much simpler!

Now, let’s add our CSS for the outer elements

```scss
.hamburger {
  margin: 0 auto;
  
  &-button {
    border-radius: 100px;
    display: inline-block;
    cursor: pointer;
    background: linear-gradient(to right, #485563, #29323c);
    // the padding along with the inner wrap adds up to 6rem*6rem circle
    padding: 1.85rem 1.5rem;
  }
}
```

Let's now add our icons. Some things can be combined into a more concise code, but we won't do that here as I want to keep things as easy to understand and follow as possible.

```scss
  .icon-opened {
    height: 2.3rem;
    width: 3rem;
    position: relative;
    .bar {
      border-bottom: 0.33rem solid white;
      position: absolute;
      display: block;
      width: 100%;
      &:first-of-type {
        top: 0;
      }
      &:nth-of-type(2) {
        top: 1rem;
      }
      &:nth-of-type(3) {
        top: 2rem;
      }
    }
  }
  .icon-closed {
    height: 2.3rem;
    width: 3rem;
    position: relative;
    .bar {
      border-bottom: 0.33rem solid white;
      position: absolute;
      display: block;
      width: 100%;
      &:first-of-type {
        top: 1em;
        transform: rotate(45deg);
      }
      &:nth-of-type(2) {
        top: 1em;
        transform: rotate(-45deg);
      } 
    }
  }
```

At this point, you should have two icons sitting next to each other, like this.

![Icons Side-by-Side](https://thepracticaldev.s3.amazonaws.com/i/vjv8bfitg1m3k4bh80ri.png)

Once we got here, we can now start working on animation, the JavaScript code is the same as we used before, simply toggling a class on our outer `.hamburger` element. We will make use of CSS 3D transform properties and positioning.

First, we need to flip the X icon and place it on the backside of the button. To do so we will absolute position the `.hamburger-button` elements, and add a transform property to our `-back` element. We will also add `backface-visibility: hidden;`, the property name should sound pretty self-explanatory, but it hides the backside of an element, it is normally applied to ‘flippable’ elements to prevent flickering caused by z-indexing and layer glitches.

```scss
 &-button {
    border-radius: 100px;
    display: inline-block;
    cursor: pointer;
    background: linear-gradient(to right, #485563, #29323c);
    // the padding along with the inner wrap adds up to 6rem*6rem circle
    padding: 1.85rem 1.5rem;
    position: absolute;
    top: 0;
    left: 0;
    backface-visibility: hidden;
    &-back {
      transform: rotateY(180deg);
    }
  }
```

We will also add pre-defined dimensions and transition properties to our main `.hamburger` element, as well as `transform-style: preserve-3d` to make sure that the nested elements are also rendered in 3d space.

```scss
.hamburger {
  margin: 0 auto;
  width: 6rem;
  height: 6rem;
  transition: all 300ms ease-in;
  transform-style: preserve-3d;
}
```

The only thing left to do now is flip the entire `.hamburger` element when the `opened` class gets added!

End result:

{% codepen https://codepen.io/saintasia/pen/pooKbNV %}

These 3 micro-interactions can be done fully in CSS without SVG or any JS libraries, and although pretty simple, they can be adjusted to look more smooth or complex with some extra-rotation, transition timing, and other properties. If you look around the web you will notice that most hamburger icons will use one of the above as a basis for their interaction, and rightfully so! The above eliminates any need to use keyframes or fancy libraries while providing a simple solution. Are you aware of any other easy-to-replicate hamburger icon interactions? If so, would love to hear about those too!