CSS sidebar toggle

CSS sidebar toggle presented in this post is made with CSS only. These days accessibility is pretty important stuff and, because changing the state of the elements cannot be done without JavaScript, I've added a small snippet for this feature.

Preparation

As I was preparing this post, I've created a CSS sidebar toggle demo on Codepen. Suddenly the pen received a large number of visitors. The pen was listed in "Picked Pens" section on Codepen home page as I discovered later. Apparently, it was a great inspiration for the community, ending up with many hearts and many forks. At this point, I knew that I was doing something good. But what I did not expect was a post about this technique.

Jorge C.S. Cardoso published a post based on this demo. It was listed in the Codepen newsletter. Well done, Mr. Cardoso, and thank you for saving me some time to write this post.

In this post, I'll try to explain CSS sidebar toggle technique in general and focus on accessibility.

Concept

In order to trigger the sidebar overlay, we'll need the following components:

  • a label,
  • a checkbox and
  • a sidebar.

We'll use checkbox's :checked pseudo class to determine whether to show or to hide the sidebar.

For a menu toggle indicator, we could use well know hamburger menu. There are many simple and awesome ways how we could do it. I've decided to use pure CSS solution, well, because this is a pure CSS sidebar toggle solution.

First demo

HTML for the first demo

<input type="checkbox" id="menuToggler" class="input-toggler"/>
<label for="menuToggler" class="menu-toggler">
  <span class="menu-toggler__line"></span>
  <span class="menu-toggler__line"></span>
  <span class="menu-toggler__line"></span>
</label>

Inside the label, we should place span elements—each one will represent one hamburger line.

Make sure to add **id** attribute on a checkbox input and matching for attribute on a label element.

CSS for the first demo

// variables
:root {
  --toggler-size: 30px;
  --toggler-line-number: 3;
  --toggler-line-size: calc(var(--toggler-size) / (var(--toggler-line-number) + var(--toggler-line-number) - 1));
  --toggler-offset-left: 10px;
  --toggler-offset-top: 10px;
  --toggler-color: Tomato;
}

// same as var(--toggler-line-number)
$total: 3;

For this demo, I've decided to use a new CSS variables feature. Beware of the support:

Can I Use css-variables? Data on support for the css-variables feature across the major browsers from caniuse.com.

Using CSS variables we could define menu icon size, the number of lines, top offset, left offset and background color.

I didn't find an efficient way how to convert a CSS variable into a SASS variable. If you know how to do this, please let me know.

The number of lines could vary. If we want 3 hamburger lines, we should add 3 span elements: <span class="menu-toggler__line"></span>. We should also set CSS variables --toggler-line-number and $total to 3.

I've tested this code with 3 and 4 hamburger lines and the code is working pretty good.

For deeper understanding how this hamburger menu is working, check the source code. If you don't get it, leave a comment below or ask a question on Twitter.

The logic is straightforward:

  • if not checked, display hamburger icon;
  • if checked, display close icon.

We are using transitions and transforms to animate the icon.

There are many ways how a sidebar could be displayed. I've decided to go with a full-width sidebar containing just a menu list.

Second demo

HTML for the second demo

In order to create CSS sidebar toggle, we should use the following HTML structure:

<input type="checkbox" id="menuToggler" class="input-toggler"/>
<label for="menuToggler" class="menu-toggler">
  ...
</label>
<nav class="sidebar">
  ...
</nav>
<main class="content">
  ...
</main>

Notice that checkbox input, label, sidebar and content are all siblings.

CSS for the second demo

:root {
  --sidebar-width: 100%;
}

.sidebar {
  width: var(--sidebar-width);
  transform: translateX(calc(var(--sidebar-width) * -1));
  ...
}

.input-toggler {
  ...

  &amp;:checked ~ .sidebar {
  transform: translateX(0);
  opacity: .98;
  }
}

The idea is to hide a sidebar by translating it out of viewport using translateX property. Then, when menu icon is clicked and :checked stated is active, translate sidebar back to the viewport.

CSS sidebar toggle accessibility

Last time I've published a post about pure CSS tabs solution, I've received a lot of comments about missing accessibility. As a result, I've updated a demo with accessibility and published a new article. This time I've decided to implement accessibility right away.

The first step is adding wai-aria attributes. We want our label to act as a button. We should add role="button" attribute. Button role supports 2 states, pressed and expanded. Let's add those too: aria-pressed="false" and aria-expanded="false". Finally, we should add aria-label="Navigation button" because there is no text element inside the label.

Next, we should add wai-aria attributes on navigation. On nav element we should add these attributes: role="navigation", aria-labelledby="menuTogglerLabel" and aria-hidden="true". The role is navigation, an element is labeled by navigation button and it is hidden by default.

The final element is a menubar with 2 wai-aria attributes: role="menubar" and aria-orientation="vertical". First attribute defines the role this element has and the second attribute describes how the element is oriented—in our case vertically. Every menubar should have menu items. They are usually links and we define them as follows: role="menuitem". Because the navigation is hidden by default, we're adding tabindex="-1" to skip tabbing through invisible elements.

Finally, we should add JavaScript snippet to change states of this elements:

let menuToggler = document.getElementById('menuToggler')
let menuTogglerLabel = document.getElementById('menuTogglerLabel');
let sidebar = document.getElementById('sidebar');
let menuItems = document.getElementsByClassName('menu__link');

menuToggler.addEventListener('change', function() {
  if(menuToggler.checked) {
    menuTogglerLabel.setAttribute('aria-pressed', 'true');
    sidebar.setAttribute('aria-hidden', 'false');
  } else {
    menuTogglerLabel.setAttribute('aria-pressed', 'false');
    sidebar.setAttribute('aria-hidden', 'true');
  }

  for(let menuItem of menuItems) {
    if(menuToggler.checked) {
      menuItem.setAttribute('tabindex', '0');
    } else {
      menuItem.setAttribute('tabindex', '-1');
    }
  }
});

When a menu button is pressed, we should change its aria-pressed attribute and navigation's aria-hidden attribute accordingly. We should change tabindex of every menu item:

  • if a menu is visible, menu items should be tabbable;
  • if a menu is not visible, menu items should not be tabbable.

Keyboard interaction

Our job is not done yet, we should implement keyboard interaction. I'll leave this part to you as this feature is out of the scope of this article. For more details see official W3C documentation for menu and menubar and navigation menubar example.

Conclusion

There are many ways how we could build UI components and there are no right way to do so. Some will disagree that the pure CSS solutions are usable in production environment, but that depends on project type and many other factors. I love to experiment with CSS because it gives me a new opportunity to learn and use the latest techniques out there. I don't know if you noticed, but beside CSS variables, I've used CSS locks and system fonts techniques in this demo. Pretty awesome, right?

And there is accessibility, which is always hard. But hard doesn't mean we should avoid learning it and using it. I encourage every developer to read this document and then try to implement any part of accessibility in their newest project. Let's make Internet a better place.

I'm always opened for discussion so leave a comment or ping me on Twitter.