Simple Slide-Out Mobile Menu with React Hooks

jarodpeachey

Jarod Peachey

Posted on April 26, 2020

Simple Slide-Out Mobile Menu with React Hooks

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!

💖 💪 🙅 🚩
jarodpeachey
Jarod Peachey

Posted on April 26, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related