How to Add a Sliding Sidebar Menu to Your Site
Tags: css, beginners, webdev, html
Published: 2022-09-19 19:01:32

Here's what we are going to build:

{% codepen https://codepen.io/tahazsh/pen/LYmywjx %}

I'll explain only the relevant code below. If you want to see the full code, check the [codepen](https://codepen.io/tahazsh/pen/mdLmOLb) above.

## The HTML

```html
<body>
  <div class="menu-sidebar">
    <div class="sidebar-content">
      <button class="close-menu-button">
        <!-- close button icon -->
      </button>
    </div>
  </div>
  <div class="overlay"></div>
  <div class="main-page">
    <div class="header">
      <button class="menu-button">
        <!-- menu icon -->
      </button>
    </div>
    <h1>Page Title</h1>
    <p>
      Lorem ipsum dolor sit amet...
    </p>
  </div>
</body>
```

This HTML consists of four main parts:
- The sidebar: `.menu-sidebar`
- The main page section: `.main-page`
- The button that opens the sidebar: `.menu-button`
- And the overlay displayed on top of the main page when the sidebar is open: `.overlay`

We have two states for this UI: idle and sidebar-open states. We specify which state is on by setting a class on the `<body>` – it can be any wrapper element, but I'm using `body` for this example. For this demo I used `sidebar-open` class for the sidebar-open state.

We will define the styling for each state using CSS, and when the `sidebar-open` class is toggled on `body`, the correct styling would be applied, which will be animated with `transition`.

Here's an example for defining styles for each state:

```css
.main-page {
  // Styling for the main page when in idle state
}

body.sidebar-open .main-page {
  // Styling for the main page when in sidebar-open state
}
```

## The CSS

```css
/* Sidebar-open state for body */
body.sidebar-open {
  overflow: hidden;
}

/* Idle state for the sidebar */
.menu-sidebar {
  width: 500px;
  height: 100%;
  overflow: auto;
  position: fixed;
  left: 0;
  top: 0;
  z-index: 2;
  transition: 0.5s ease translate;
  translate: -100% 0;
  will-change: transform;
}

/* Sidebar-open state for the sidebar */
body.sidebar-open .menu-sidebar {
  translate: 0 0;
}

/* Idle state for the main page */
.main-page {
  transition: 0.5s ease translate;
  will-change: transform;
}

/* Sidebar-open state for the main page */
body.sidebar-open .main-page {
  translate: 500px 0;
}

/* Idle state for the overlay */
.overlay {
  opacity: 0;
  pointer-events: none;
  background: rgba(0, 0, 0, 0.5);
  position: fixed;
  top: 0;
  left: 0;
  z-index: 1;
  width: 100%;
  height: 100%;
  transition: 0.5s ease opacity;
}

/* Sidebar-open state for the overlay */
body.sidebar-open .overlay {
  opacity: 1;
  pointer-events: all;
}

/* When on tablet-size screen, make the sidebar open in full width above the main page */
@media (max-width: 768px) {
  .menu-sidebar {
    width: 100%;
  }
  body.sidebar-open .main-page {
    translate: 0 0;
  }
}
```

Let's see how the elements will be in each state.

**The sidebar (`.menu-sidebar`):**
- We don't want the sidebar to be part of the page flow, so we are positioning it with `position: fixed`.
- To make it appear above the main page when open, we need to set its `z-index` to something bigger than `.overlay` and the main page. So we set it `z-index: 2`.
- I've chosen its width to be `500px` but you can choose any width you want.
- In idle state, the sidebar should be positioned outside the viewport. Setting `translate: -100% 0` will move it to the left by its full width.
- For better performance, we will animate the sidebar using `translate`, and to make that even better, we need to promote the element to a new layer. That's what `will-change` does.
- In sidebar-open state, the sidebar `translate` will be `0 0` which means it will move back to its original position, and with `transition` it will appear as animating to the right.

**The main page (`.main-page`):**
- We don't need to do anything when in idle state. We just need to make sure it has `transition` and `will-change` for animation purposes.
- But when in sidebar-open state, this element needs to move to the right by the same width as the sidebar, which is `500px` in this case.

**The overlay (.overlay):**
- It's a fixed element with full width and height to cover the whole screen. It also has a background with transparency to see the main page below it `background: rgba(0, 0, 0, 0.5)`.
- It should be hidden when in idle state. We are using `opacity: 0` here and not `display: none` because we want to animate it with `transition: 0.5s ease opacity`.
- An element with `opacity: 0` still exists on the page but not seen. So clicking anything on the page would not work because you will be clicking on `.overlay`. To fix this, we set `pointer-events: none`, which disables all mouse and touch events on it.
- When in sidebar-open state, we just need to show it and enable clicking on it (so it closes the sidebar when you click on it). To do this, we set `opacity: 1` and `pointer-events: all `.

## The JavaScript

The last step to make this work is toggling `sidebar-open` class on body when the menu button, overlay, or close button is clicked.

```js
const menuButton = document.querySelector('.menu-button')
const overlay = document.querySelector('.overlay')
const closeMenuButton = document.querySelector('.close-menu-button')

menuButton.addEventListener('click', toggleMenu)
overlay.addEventListener('click', toggleMenu)
closeMenuButton.addEventListener('click', toggleMenu)

function toggleMenu() {
  document.body.classList.toggle('sidebar-open')
}
```