Simple Slide-Out Mobile Menu with React Hooks
Tags: react, css, javascript, design
Published: 2020-04-26 12:15:00

If you're a front-end developer, I can almost guarantee that you've worked with a mobile menu at some point in your career. If you have, you know first-hand that, sometimes, crafting mobile menus can be a little difficult.

Just this month, I was working on a website of mine, and I came across one of these issues. I wanted to create a mobile menu with a few simple requirements:

 1. It slides out from under the header
 2. That's it

Seriously, it's not like I wanted to add 18 different animations, transitions, and effects. I just wanted to make the menu slide out from underneath the header. Simple, right?

Nope. 

As I found out, it wasn't as simple as setting the z-index of the menu to be less than that of the header. And trust me, I set the z-index to `999999` just to see what would happen (spoiler: nothing).

After browsing through Stackoverflow, Github, and other forums for way too long, I eventually found a solution. The solution made perfect sense after I found it, but I'm writing this post anyway so others can easily find it as well.

## The Structure
For this tutorial, I'm using React with styled-components.

Why?

Because that's what I built it with.

Anyway, I'm going to go through this assuming you already have your app set up, with a header and all that good stuff.

If you don't, go ahead and set up a basic React project.

OK. If you don't already have one, create a `header.js` file. I don't care where.

To start, we're going to create a functional component and structure the header and mobile menu.

   *header.js*
```
import  React, { useState } from  'react';

export const  Header = () => {
  return (
    <Wrapper>
      <HeaderWrapper id='header'>
        <Container>
          <Title>Menu Demo</Title>
          <MenuToggle>
            <RotateContainer>
              <span  />
              <span  />
              <span  />
            </RotateContainer>
          </MenuToggle>
        </Container>
      </HeaderWrapper>
      <MenuWrapper>
        <Menu>
          <MenuItem href='/'>Home</MenuItem>
          <MenuItem href='/'>About</MenuItem>
          <MenuItem href='/'>Contact</MenuItem>
        </Menu>
      </MenuWrapper>
    </Wrapper>
  );
};
```
Since we're using styled-components, each component is named accordingly.

Oh, and remember when I said I spent forever messing with the `z-index` of the menu, and it didn't work?

That's because the mobile menu must be a *sibling* of the header, **not** a child. It doesn't work if it a child element.

Anyways. After we have the basic structure, we need to style each item.

## Styling the menu

**Styling the Wrapper**

This isn't very exciting, to be honest. It's got three lines of CSS in it.

But, still very important.

Place this styled-component at the bottom of your `header.js` file:

```
const  Wrapper = styled.div`
  * {
    box-sizing: border-box;
  }
`;
```

The `box-sizing` rule is needed to size elements with margin and padding properly. However, you most likely won't need this, as your root CSS file probably sets the same rule.

**Styling the Header Wrapper**

The header wrapper is also optional. If you already have a header on your website (very important, you probably should), then you can keep your current styling.

If not, add a new `HeaderWrapper` styled-component to your file:

```
const  HeaderWrapper = styled.header`
  padding: 18px 0;
  color: white;
  position: fixed;
  background: tomato;
  left: 0;
  top: 0;
  right: 0;
  bottom: auto;
  z-index: 999;
`;
```

There's one specific line of CSS that is very important: `z-index: 999;`

To display the header over the top of the menu, you need to make sure the header component has a z-index higher than the mobile menu.

Moving on.

**Styling the Menu Toggle**

I took a little detour on the mobile menu toggle and added some pleasing transitions, using `<span>` tags.

At the bottom of your file, add two new styled-components: one for the toggle, and one to handle the rotation of the toggle.

```
const  MenuToggle = styled.div`
  z-index: 9999;
  width: 30px;
  height: 30px;
  transform: rotate(0deg);
  transition: all 0.25s ease-in;
  cursor: pointer;
  margin-left: auto;
  span {
    display: block;
    position: absolute;
    height: 4px;
    width: 100%;
    background: white;
    border-radius: 9px;
    opacity: 1;
    left: 0;
    transform: rotate(0deg);
    transition: ${(props) =>
    props.open ? 'all 0.25s ease-in' : 'all 0.25s ease-out'};
  } 
  span:nth-child(1) {
    top: ${(props) => (props.open ? 'calc(50% - 2px)' : '10%')};
    transform-origin: left center;
  }
  span:nth-child(2) {
    top: ${(props) => (props.open ? 0 : 'calc(50% - 2px)')};
    left: ${(props) => (props.open ? 'calc(50% - 2px)' : null)};
    width: ${(props) => (props.open ? '4px' : null)};
    height: ${(props) => (props.open ? '100%' : null)};
    transform-origin: left center;
  }
  span:nth-child(3) {
    top: calc(90% - 4px);
    transform-origin: left center;
    width: ${(props) => (props.open ? 0 : null)};
    opacity: ${(props) => (props.open ? 0 : 1)};
  }
`;

const  RotateContainer = styled.div`
  height: 100%;
  width: 100%;
  transition: ${(props) => props.open ? 'all 0.25s ease-in-out' : 'all 0.25s ease-in-out'};
  transform: ${(props) => (props.open ? 'rotate(-45deg)' : 'none')};
`;
```

To summarize that, we're adding three `<span>` tags inside the `MenuToggle` component. The `<span>` tags are rotated depending on whether the menu is open or not.

You may notice the variables inside the styled-components: `width: ${(props) => (props.open ? 0 : null)};` These are what decide whether or not to show the mobile menu, rotate the toggle, or apply other menu-related styling. We'll be adding that functionality later on.

**Styling the Menu**

Don't worry, this is the last one, I promise.

The menu styling also depends entirely on your preference, but, once again, there are some important lines of CSS that you need.

```
const  MenuWrapper = styled.div`
  position: fixed;
  overflow: hidden;
  top: ${(props) => (props.open ? '0' : '-100%')};
  left: 0;
  z-index: 0;
  margin-top: 66px;
  width: 100%;
  transition: ${(props) =>
  props.open ? 'all 0.25s ease-out' : 'all 0.6s ease-out'};
  box-shadow: 0px 4px 20px -5px #e8e8e8;
  padding: 12px;
`;
```

If you have your own styling, simply add in these 5 lines (the very important ones):

```
position: fixed;
overflow: hidden;
top: ${(props) => (props.open ? '0' : '-100%')};
left: 0;
z-index: 0;
```

## Adding the functionality

Great! So far, we have nothing useful. We've got a menu that doesn't close, which is probably not the best thing for your website. Luckily, we're not done yet.

To open and close the menu, we need to set an `open` state that tells each component whether or not the menu is open.

To do this, we're using the React `useState()` hook.

Inside your functional component, add the state, as well as a toggle function that will set the menu to open or closed:

```
const Header = () => {
  const [open, setOpen] = useState(false);
 
  const  toggleMenu = () => {
    setOpen(!open);
  };
  
  ...
}
```

Now that we have our `open` state, we need to call the `toggleMenu()` function when we click on the menu toggle.

Update your `<MenuToggle>` component to look like this:

```
<MenuToggle onClick={toggleFunction} open={open}>
  <RotateContainer open={open}>
    <span  />
    <span  />
    <span  />
  </RotateContainer>
</MenuToggle>
```

Now, when you click on the menu toggle, it should switch from a hamburger menu to an X. The menu doesn't show up yet, because we haven't hooked it up to the `open` state yet.

Let's do that now.

Update your `<MenuWrapper>` and `<Menu>` components:

```
<MenuWrapper open={open}>
  <Menu open={open}>
    <MenuItem href='/'>Home</MenuItem>
    <MenuItem href='/'>About</MenuItem>
    <MenuItem href='/'>Contact</MenuItem>
  </Menu>
</MenuWrapper>
```

And OPEN SESAME! A mobile menu now appears when you click the hamburger menu 🎉 (guess what happens when you click the X)

So, we now have a functioning mobile menu that slides out from underneath the header, as well as a menu toggle that looks pretty sweet!

If you liked this article, please leave a like.

Thanks for reading!