Hamburger Menu Icon

Mobile "hidden" Menu without JavaScript

One of my biggest pet peeves in looking at code from other developers is how needlessly convoluted they make the simplest of tasks, and how much garbage they put in the markup to do something simple. One of the best examples of such pointless code is the current practice of "hiding the menu" for mobile devices.

You have systems like Bootstrap that use a horde of markup, is SUPPOSED to be a CSS framework, and STILL resorts to JavaScript to get the job done. There's a reason I call that disaster of developer ineptitude "bootcrap", seriously! Go find a stick to scrape that off with!

Worse you have some folks out there saying to make two copies of the same menu, one to be shown on mobile, one for desktop... What in blazes are they doing that couldn't simply have been handled by restyling the SAME markup? What do they think they need that extra copy for?!?

The practice itself is a good one. Mobile devices don't have a lot of screen space, you need larger buttons for "finger sized" tap targets, and hiding the menu behind a selection button means users can get at what they SHOULD be going to a website for -- the CONTENT -- all that much faster!

Laughably, all that code and junk they piss all over the markup with? I can do it with a normal unordered list menu, with a <input type="checkbox"> and <label> before it! You see, CSS 3 has this wonderful new attribute called "checked" that checkboxes have, which you can use to target other elements alongside it with. Likewise there's the "adjacent sibling" selector of "+", and the "all siblings selector" of "~" that we can leverage to not have to bloat out the markup with a bunch of classes to fill it out with. Even BETTER we can use generated content to plug in our text or images inside the label. The for attribute on the label has this magical ability to make clicking on it behave as if you clicked on the input element it is for.

All these CSS3 things we usually would avoid in general use as they aren't available on older desktop browsers, but the entire point of hiding the menu is for responsive layouts, something that by definition means CSS3 is available!

The markup

Putting that all together, we end up with this relatively simple markup:

<input type="checkbox" id="menuShowHide">
<label for="menuShowHide"></label>
<ul id="mainMenu">
	<li><a href="#">Home</a></li>
	<li><a href="#">Forums</a></li>
	<li><a href="#">Tutorials</a></li>
	<li><a href="#">Reviews</a></li>
	<li><a href="#">Links</a></li>
	<li><a href="#">Contact Us</a></li>
</ul>

That's it, the entire thing needed... It does violate one of my "good practices" rules of not abusing form elements outside of a form, but I'm willing to make an exception in this case as the result is just too slick. Again, even when you have rules, you have to weigh the penalties and there are ALWAYS exceptions!

Compare that to the rubbish bootcrap would have you vomit up to do the same job!

<nav class="navbar navbar-default">
	<div class="container-fluid">
		<div class="navbar-header">
			<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
				<span class="sr-only">Toggle navigation</span>
				<span class="icon-bar"></span>
				<span class="icon-bar"></span>
				<span class="icon-bar"></span>
			</button>
			<a class="navbar-brand" href="#">Project name</a>
		</div>
		<div id="navbar" class="navbar-collapse collapse">
			<ul class="nav navbar-nav">
				<li><a href="#">Home</a></li>
				<li><a href="#">Forums</a></li>
				<li><a href="#">Tutorials</a></li>
				<li><a href="#">Reviews</a></li>
				<li><a href="#">Links</a></li>
				<li><a href="#">Contact Us</a></li>
			</ul>
			<ul class="nav navbar-nav navbar-right">
				<li class="active"><a href="./">Default <span class="sr-only">(current)</span></a></li>
				<li><a href="../navbar-static-top/">Static top</a></li>
				<li><a href="../navbar-fixed-top/">Fixed top</a></li>
			</ul>
		</div><!--/.nav-collapse -->
	</div><!--/.container-fluid -->
</nav>

... and then people wonder why I call it bootcrap!?! The PINNACLE of developer ineptitude. You mix in that microformat aria role garbage that no legitimate user-agent is likely to ever use for anything, and the HTML 5-tard redundant "NAV" tag, and is it any wonder I consider such things to be rubbish? It's 2k of code doing a third of a K's job!!!

You figure in the scripting needed to make theirs work, the CSS needed, and it begs the question "How is this making it easier?" -- It isn't!

Ok, enough ranting! How do we make it work?

The first step is as I often say to start with your desktop layout. Anythign we can't target using media queries (like Internet Exploder 8 and earlier) really has to be our starting point. Let's just make this a simple one row of inline-block centered elements. First hide the checkbox -- slide it off screen with absolute positioning instead of display:none as some browsers won't let you "focus" or change a display:none input!

From there it's just a matter of stripping off the bullets, setting text align, pad the list top and bottom, margin the bottom to push the content away, colour the background, set the list items to display:inline so it's like they don't even exist, and inline-block the anchors to style them all purty.

#menuShowHide {
	position:absolute;
	left:-999em;
}

#mainMenu {
	list-style:none;
	text-align:center;
	padding:0.25em 0;
	margin-bottom:1em;
	background:#F0F8FF;
	border-bottom:2px solid #248;
}

#mainMenu li {
	display:inline;
}

#mainMenu a {
	display:inline-block;
	padding:0.25em 1em;
	text-decoration:none;
	color:#000;
}

#mainMenu a:active,
#mainMenu a:focus,
#mainMenu a:hover {
	background:#ACE;
}

In other words, nothing out of the ordinary for a CSS styled menu list.

To make it have our responsive behavior, we need to use a media query to say when to switch to our responsive layout. When that happens we first need to hide the menu.

@media (max-width:38em) {
	#mainMenu {
		display:none;
	}

Then we move on to the real magic. First we make the label have "Show Menu" or "Hide Menu" text based on whether or not the checkbox is :checked

	#menuShowHide + label:before {
		content:"Show menu";
	}
	#menuShowHide:checked + label:before {
		content:"Hide Menu";
	}

In your production code you could make that say whatever you like, show whatever symbols you wanted, etc, etc...

From there it's just a matter of making the menu show when checked, which is where our "any sibling selector" of "~" comes into play.

	#menuShowHide:checked ~ #mainMenu {
		display:block;
	}

You could also select that as #menuShowHide:checked + label + ul but that can get a bit wonky to keep track of.

From there it's just a simple matter of styling the anchors, LI and so forth as desired, hence why my final code in the demo is this:

@media (max-width:38em) {
	body {
		padding:0;
		background:#248;
	}
	#mainMenu {
		display:none;
		overflow:hidden; /* wrap floats and margins */
		padding:0.25em;
		border-top:2px solid #248;
	}
	#mainMenu li {
		float:left;
		width:50%;
	}
	#mainMenu a {
		display:block;
		padding:0.75em;
		margin:0.25em;
		background:#DEF;
	}
	#menuShowHide + label {
		display:block;
		padding:1em;
		margin-bottom:1em;
		text-align:center;
		font-weight:bold;
		background:#036;
		color:#FFF;
	}
	#menuShowHide + label:active,
	#menuShowHide + label:focus,
	#menuShowHide + label:hover {
		background:#06C;
	}
	#menuShowHide + label:before {
		content:"Show menu";
	}
	#menuShowHide:checked + label {
		margin:0;
	}
	#menuShowHide:checked + label:before {
		content:"Hide Menu";
	}
	#menuShowHide:checked ~ #mainMenu {
		display:block;
	}
}

Which leverages those LI to make two cute columns out of our buttons, and increases them to "finger sized" targets with the larger padding and margins.

As always, I've put a live demo up here:
demos/mobileMenus/template.html

With the directory...
demos/mobileMenus
... wide open for easy access to all the bits and pieces.

That's it. Some 340 bytes of markup and 1.3k of CSS doing a job most people out there are sleazing as much as 2k of HTML, 4k of CSS and 10k or more of scripttardery to accomplish.

... and we did it without JavaScript!

Any disadvantages?

Like anything else there are a couple small niggling issues to deal with... but the only one that really stands out in my mind is that the checkbox is visible CSS off. Users visiting the site on non-CSS user-agents will have an unlabelled random checkbox on the page that doesn't do anything...

I'm willing to say "fine, whatever" to that given that the alternatives - like that bootcrap code up above -- fills the page with all sorts of useless confusing gibberish since it's CSS and Scripting only controls are in the markup. It's only a small tradeoff for users who should be used to random bits that don't make sense interpsersed throughout modern pages.

But I want a Hamburger!!!

Personally, I'm not a fan of the vague hamburger icon most of these other approaches use, I'd much rather see some text saying what it does with maybe something like a rotated arrow or plus/minus signs to make the state clear. BUT...

I fully recognize that as much as it contributes to "ambiguous UI" design, it has become an interface norm for a lot of projects. The laugh is, most people would throw a whole slew of extra markup in there or images to pull it off, when again we have CSS3 and generated content available... so let's USE IT!

This takes only a few minor changes to the CSS:

	#menuShowHide + label {
		position:relative;
		float:right;
		padding:0.5em;
		margin:-4.75em 0.5em 0;
		border:2px solid #FFF;
		background:#248;
		color:#FFF;
		border-radius:0.5em;
	}
	#menuShowHide + label:active,
	#menuShowHide + label:focus,
	#menuShowHide + label:hover {
		background:#06C;
	}
	#menuShowHide + label:after,
	#menuShowHide + label:before {
		content:"";
		display:block;
		width:2em;
		height:0.5em;
		border:solid #FFF;
		border-width:0.25em 0;
	}
	#menuShowHide + label:after {
		border-top:0;
	}

... and there's that "hamburger" built entirely with CSS. I'm NOT going to go into the details of that right this moment as that's a pretty complex subject unto itself, but let's just say we style the label, use borders and background on the :before and :after elements to build that icon... and since it's all measured in EM's that icon dynamic/elastic in size too.

The working demo of that is here:
demos/mobileMenus/burger/template.html

The actual CSS...
demos/mobileMenus/burger/screen.css

... takes a bit of adjustment to handle all the proper placement of elements as the menu is shown and hidden -- but nothing anyone versed in CSS basics shouldn't be able to make sense of.

So again, hope this helps some folks out here, or at least gets you thinking about different ways of handling things. Saving a few K might not seem that important, but you spread that over an entire page of flawed bloated methodologies... it can pay off very quickly. That's why I'm always pointing out how people seem to use 50k or more markup and hundreds of K of CSS and JavaScript to do the job of 48k or less COMBINED!

Updated 23 April, 2018 -- simpler CSS for the hamburger icon.

Projects

  • elementals.js
    A lightweight JavaScript library focusing on cross browser support, ECMAScript polyfills, and DOM manipulation.
  • eFlipper.js
    An image carousel script using elementals.js
  • eProgress.js
    A JavaScript controllable progress bar using elementals.js. Based on the nProgress project that relies on the much heavier jQuery library.

/for_others

Browse code samples of people I've helped on various forums. These code snippets, images, and full rewrites of websites date back a decade or more, and are organized by the forum username of who I was helping. You'll find all sorts of oddball bits and pieces in here. You find any of it useful, go ahead, pick up the ball, and run with it.