Jump to main content

Morphing Menu Button

  • Published:
  • Category: CSS

Look Ma’, no JS!

Codrops is one of my favorite resources for front-end demos. And one of their latest articles Morphing Button Concept was quite inspiring to me as I’m currently working on a project where incorporating concepts like these would be beneficial.

However, while looking through the source code, I got to wondering if it weren’t at all possible to do some of these interactions, if not all of them, without JavaScript.

Let the Experiment Begin!

As the concept between all of these demos is relatively the same — on click/press of an element, have another element come into view for user interaction — I’m going to just stick with showing how to convert over the Sidebar Settings demo to pure CSS.

Let’s break down the HTML first, shall we?

<input type="checkbox" id="nav-expand" class="invis" />

<nav class="nav-side">
  <label for="nav-expand" class="btn-label">
    Click plz

  <ul class="menu-list">

<div class="main-base">

I’ve cut out anything that isn’t directly necessary to understanding how this demo works. Within what I’ve dubbed the main-base you can throw all sorts of fun content in. Like paragraphs about bacon, various other HTML5 elements that will swoon your co-workers and maybe even pictures of dogs and cats living together. If you’re into things like mass hysteria n’ all.


As you can see it’s pretty straight forward markup. The key here are the checkbox that we’ll be hiding via the invis class, and the <label for="nav-expand"> that will serve as our ‘button’ and means of controlling the checked / unchecked state of the checkbox.

The order of the markup here is also important as without this special ordering we would not be able to achieve all of the affects of the original Codrops demo. But to get into the details of why the ordering is important, we need to switch gears and start looking at the CSS.

Let’s get a-styling

As with the HTML, I’m going to omit some of the CSS that I’ve used for presentational purposes, so we can focus in on just what’s needed to achieve the functionality of this demo. (You can dive into all of the code here though).

The following block of CSS will get the main foundation of the initial state of our demo setup.

  This hides the checkbox from browsers
  but not from screen readers
.invis {
  height: 1px;
  left: -999px;
  position: absolute;
  top: -999px;
  visibility: hidden;
  width: 1px;

  Where the content of the page goes

  Notice the left: 0px; We need the 'px' here
  for our transition animation to work properly
.main-base {
  left: 0px;
  position: relative;
  transition: all .3s;

  The initial state of the nav
.nav-side {
  background: #E85657;
  color: #fff;
  bottom: 80px;
  height: 80px;
  left: 40px;
  overflow: hidden;
  position: fixed;
  transition: all .3s;
  width: 80px;
  z-index: 2;

  Initial state & global styling of the nav elements
  inside of our sidebar menu

  As with the note about the left: 0px for .main-base,
  notice the height: 0%; here. We need the % to effectively
  animate our transition later.
.menu-list {
  height: 0%;
  overflow: hidden;
  transition: all .3s;

  .menu-list a {
    display: block;
    padding: 12px 8px;
    text-decoration: none;

  Make the cursor look like a pointer, so on hover,
  people know they can press the label
.btn-label {
  cursor: pointer;

That covers the ground work to get our page setup in the inactive state.

Now here’s the CSS that’s needed to morph our nav ‘btn’ into a sidebar, and push the content of our page over to the right.

  The :checked pseudo class and + selector
  here are the keys to all of this.

  Here we move our .nav-side is morphing from
  it's original height, width and position to these
  new values.
#nav-expand:checked + .nav-side {
  bottom: 0;
  height: 100%;
  left: 0;
  width: 200px;

  Expands our .menu-list from 0% to 100% height
#nav-expand:checked + .nav-side .menu-list {
  height: 100%;

  Here we're changing our .btn-label from a text label
  into a close button. We do this by making our text
  transparent (to hide the original 'click plz' text,
  changing the label to an inline-block,
  and giving it a height/width to reduce it's size
#nav-expand:checked + .nav-side .btn-label {
  color: transparent;
  display: inline-block;
  height: 20px;
  line-height: 20px;
  text-align: center;
  width: 20px;

  Here we introduce our 'X' using the ::before pseudo class
  since we aren't using JS to introduce different markup /
  don't want to use more markup than we need to
#nav-expand:checked + .nav-side .btn-label::before {
  color: #000;
  content: "X";
  line-height: 20px;

  This final bit moves our page over to the right
  and is a further example of why the order of the
  markup is important, as we specifically need to
  chain our elements together with the + selector
#nav-expand:checked + .nav-side + .main-base {
  left: 200px;

And that’ll do it!

As mentioned before, the importance of the markup here is key. By having the checkbox as the first element in our cascade, and then having the other elements we need to interact with be siblings of that checkbox, we can use the + selector to chain all of those elements together.

It makes for longer selectors than I’d normally feel comfortable with, but we’re using a lot less CSS here compared to the JavaScript needed to achieve a very similar effect.

So, how useful is all of this?

That’s for you to decide really. The great thing about the Codrops example is that, while not as minimal as the CSS, the Javasript will let your markup be a lot more flexible. And while I didn’t test it, I imagine their Javasript will also give a more consistent experience in older browsers as well. This demo requires that you are using browsers that can support CSS3 transitions and all. The demo still ‘works’ in older browsers, but the transition animation is one of the cooler things about this demo. Without that, there’s less user delight.

Here’s a full page working example of this demo.

And here’s the codepen page that includes the working demo along with the full source code.

See the Pen Expanding Nav Element by Scott (@scottohara) on CodePen.