Creating a fold out navigation with CSS only
Tags: css, tutorial, codepen, beginners
Published: 2019-12-29 15:29:55

---
title: Creating a fold out navigation with CSS only
published: true
description: Fold it, flip it, hide it using pseudo classes like a pro!
tags: css, tutorial, codepen, beginners
cover_image: https://thepracticaldev.s3.amazonaws.com/i/jcvu4dmd26hpi79gahom.jpg
---

**While using JavaScript to open a mobile navigation is a solid option, it's also possible to do it with CSS only. Even submenus don't necessarily need any JS to work, [pseudo classes](https://developer.mozilla.org/en-US/docs/Web/CSS/Pseudo-classes) and [sibling selectors](https://developer.mozilla.org/en-US/docs/Web/CSS/General_sibling_combinator) are the answer!**

### How
Fold out navigations are usually triggered by what's been lovingly called the hamburger menu, referring to the three horizontal lines. This has been a much debated icon, but people have gotten used to it, so for now let's stick to it.

![Opening and closing a fold out navigation](https://thepracticaldev.s3.amazonaws.com/i/ha3r9ov3gj2w1r2xf60t.gif)

To create a CSS only navigation we need to use an `<input />` element with `type="checkbox"`, we will use the `:checked` pseudo class to see if it's selected. And we need a `<label>` that's linked to the input element with a [`for`](https://www.w3schools.com/tags/att_for.asp) attribute.
This is the simplified version of what I usually create, for clarity's sake I've styled on elements rather than classes:

```html
<nav>
  <input type="checkbox" id="trigger" />
  <label aria-label="open main navigation" for="trigger"><span></span></label>
  <ul>
    <li>
      <a href="#">Link</a>
    </li>
    <li>
      <a href="#">Link</a>
    </li>
    <li>
      <a href="#">Link</a>
    </li>
  </ul>
</nav>
```
Note that I put the label element after the input element, this makes it possible to style the label by the input's state (`:focus`, `:checked`, etc).


#### Hiding the checkbox
The checkbox input needs to be entirely gone. I added position fixed because the rest of the navigation is fixed and standard behaviour of clicking on a checkbox label is scrolling to it, which would mean scrolling to the top.

```css
input {
  opacity: 0;
  width: 0;
  height: 0;
  appearance: none;
  position: fixed;
}
```


#### Creating the trigger
You can just add a background image of a hamburger icon here or you can use before and after selectors to create them. This is the visible part of the trigger, this will be your "nav is closed!"-state.

You might have wondered about the 'unnecessary' `span` inside the `label` tags, I added it to make it styling it easier, you can leave the span out.

```css
label {
  position: fixed;
  top: 0;
  right: 0;
  z-index: 10;
  display: flex;
  align-items: center;
  width: 15px;
  height: 10px;
  margin: 40px;
}

label span {
  display: block;
  width: 100%;
  background: hotpink;
  height: 2px;
  transition: transform 1s;
}

label span::before,
label span::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  display: block;
  height: 2px;
  background: hotpink;
  transition: 0.4s
}

label span::before {
  top: 0;
}

label span::after {
  top: calc(100% - 2px); /* to make the transition easier :) */
}
```


#### Hiding the navigation
While you could just add `display: none` here, and `display: block` when you want to show it, using a transform is a lot more fun.
I chose to translate the `ul` 100vw on the X axis to hide it from the screen until the user clicks on the trigger.

```css
ul {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  transform: translateX(100vw);
  transition: transform 0.2s;
  background: white;
}
```


#### Creating the 'open state'
I talked earlier about using the :checked pseudo class, a pseudo class is a keyword that's added to an element when it has a certain state.

Radio- and checkbox input types have a `:checked` state. Checkboxes can be checked and unchecked by clicking on them, in order to select another radiobutton there needs to be another button. Checkboxes are more suitable for our goals so we use them.

This is the place to add the css for the nav trigger's open state, a lot of websites don't style and animate this and it's a shame in my opinion.

The code may look intimidating if you're new to css but if you read it out loud it sometimes helps to understand it more clearly. The first line for example: select the inner element 'span' of the sibling 'label' of the checked 'input'.
Okay that still sounded a bit like gibberish, practice really does make perfect here.

```css
input:checked + label span {
  transform: rotate(45deg);
}

input:checked + label span::before, input:checked + label span::after {
  top: calc(50% - 1px);
  transition: 0.4s;
}
input:checked + label span::after {
  transform: rotate(90deg);
}
```


#### Showing the navigation

I used the general sibling selector here, this means "select any ul that is a sibling of a checked input", `input:checked + label + ul` would also have worked, but is more sensitive to errors (what if you add an element in between?).

```css
input:checked ~ ul {
  transform: none;
  transition: transform 0.4s; 
}

```

The pretty SCSS version with beziers and all the fancy stuff: 

{% codepen https://codepen.io/Sidstumple/pen/RwNZYBe %}


### But why?
Because we can!

This may seem a lot more complicated than just adding a class with JavaScript and you may be right. This is not only a great exercise if you want to get better at handling pseudo classes and sibling selectors, it's also very sturdy. Imagine your website has a small error in the JS, this would mean people couldn't use your navigation anymore. Let's leave JavaScript for the crazy animations and keep simple things like opening and closing a navigation as a CSS thing.


### Accessibility 
A good point to throw away this entire article is that using an input element to open and close a navigation is not [semantic](https://boagworld.com/dev/semantic-code-what-why-how/).

But really if you think about it, most people using screen readers (the group that might notice that you used an `input` element in stead of a more semantic `button` element) aren't exactly developers, you might really annoy the blind developers though, be ware.

The trigger is automatically accessible with the keyboard, if you also add a clear `aria-label` you've done more than most developers who use a `button` to open a fold out navigation.

I would really love some input from actual blind users or accessibility experts, let me know in the comments whether I'm completely clueless.