Horizontal Slide Out Menu

A horizontal slide out menu with a grid-like thumbnail layout for the submenu. With media query examples for smaller devices.

HorizontalSlideOutMenu

A simple, horizontal slide out menu with a grid-like thumbnail layout for the submenu. The menu slides out from the top when a main menu item is clicked and the sub-items fade in. When clicking on another item, the height of the submenu will adjust and the content will fade in and out while switching.

Some media query examples show how to make the menu responsive and change the view to a touch-friendly vertically stacked navigation.

The HTML

<nav class="cbp-hsmenu-wrapper" id="cbp-hsmenu-wrapper">
	<div class="cbp-hsinner">
		<ul class="cbp-hsmenu">
			<li>
				<a href="#">Lovely Spirits</a>
				<ul class="cbp-hssubmenu">
					<li><a href="#"><img src="images/1.png" alt="img01"/><span>Delicate Wine</span></a></li>
					<li><a href="#"><img src="images/2.png" alt="img02"/><span>Fine Spirit</span></a></li>
					<li><a href="#"><img src="images/3.png" alt="img03"/><span>Heavenly Ale</span></a></li>
					<li><a href="#"><img src="images/4.png" alt="img04"/><span>Juicy Lemonade</span></a></li>
					<li><a href="#"><img src="images/5.png" alt="img05"/><span>Wise Whiskey</span></a></li>
					<li><a href="#"><img src="images/6.png" alt="img06"/><span>Sweet Rum</span></a></li>
				</ul>
			</li>
			<li>
				<a href="#">Delicious Beverages</a>
				<ul class="cbp-hssubmenu cbp-hssub-rows">
					<li><a href="#"><img src="images/7.png" alt="img07"/><span>Lovely Slurp</span></a></li>
					<li><a href="#"><img src="images/8.png" alt="img08"/><span>Lemony Grappa</span></a></li>
					<li><a href="#"><img src="images/9.png" alt="img09"/><span>Eggy Liquor</span></a></li>
					<li><a href="#"><img src="images/10.png" alt="img10"/><span>Fresh Juice</span></a></li>
					<li><a href="#"><img src="images/1.png" alt="img01"/><span>Delicate Wine</span></a></li>
					<li><a href="#"><img src="images/2.png" alt="img02"/><span>Fine Spirit</span></a></li>
					<li><a href="#"><img src="images/3.png" alt="img03"/><span>Heavenly Ale</span></a></li>
					<li><a href="#"><img src="images/4.png" alt="img04"/><span>Juicy Lemonade</span></a></li>
					<li><a href="#"><img src="images/5.png" alt="img05"/><span>Wise Whiskey</span></a></li>
					<li><a href="#"><img src="images/6.png" alt="img06"/><span>Sweet Rum</span></a></li>
					<li><a href="#"><img src="images/1.png" alt="img01"/><span>Delicate Wine</span></a></li>
					<li><a href="#"><img src="images/2.png" alt="img02"/><span>Fine Spirit</span></a></li>
				</ul>
			</li>
			<li>
				<a href="#">Fine Liquors</a>
				<ul class="cbp-hssubmenu">
					<li><a href="#"><img src="images/10.png" alt="img10"/><span>Fresh Juice</span></a></li>
					<li><a href="#"><img src="images/6.png" alt="img06"/><span>Sweet Rum</span></a></li>
					<li><a href="#"><img src="images/9.png" alt="img09"/><span>Eggy Liquor</span></a></li>
				</ul>
			</li>
			<li><a href="#">Our Mission</a></li>
			<li><a href="#">Contact</a></li>
		</ul>
	</div>
</nav>

The CSS

@font-face {
	font-family: 'bpmenu';
	src:url('../fonts/bpmenu/bpmenu.eot');
	src:url('../fonts/bpmenu/bpmenu.eot?#iefix') format('embedded-opentype'),
		url('../fonts/bpmenu/bpmenu.woff') format('woff'),
		url('../fonts/bpmenu/bpmenu.ttf') format('truetype'),
		url('../fonts/bpmenu/bpmenu.svg#bpmenu') format('svg');
	font-weight: normal;
	font-style: normal;
}

/* Main menu wrapper */
.cbp-hsmenu-wrapper {
	position: relative;
}

/* Common style for all lists */
.cbp-hsmenu-wrapper ul {
	list-style: none;
	padding: 0;
	margin: 0 auto;
}

/* 100% width bar for menu */
.cbp-hsinner {
	background: #47a3da;
	position: relative;
	z-index: 100;
}

/* Main menu style */
.cbp-hsmenu-wrapper .cbp-hsmenu {
	width: 90%;
	max-width: 69em;
	margin: 0 auto;
	padding: 0 1.875em;
}

.cbp-hsmenu > li {
	margin-left: 4em;
	display: inline-block;
}

.cbp-hsmenu > li:first-child {
	margin-left: 0;
}

/* Main menu link style */
.cbp-hsmenu > li > a {
	color: #fff;
	font-size: 1.2em;
	line-height: 3em;
	display: inline-block;
	position: relative;
	z-index: 10000;
	outline: none;
}

.no-touch .cbp-hsmenu > li > a:hover,
.no-touch .cbp-hsmenu > li > a:focus,
.cbp-hsmenu > li.cbp-hsitem-open > a {
	color: #02639d;
}

/* Add an arrow to the main menu link if it has a submenu (not the only child) */
.cbp-hsmenu > li > a:not(:only-child):before {
	display: inline-block;
	font-family: 'bpmenu';
	speak: none;
	font-style: normal;
	font-weight: normal;
	font-variant: normal;
	text-transform: none;
	line-height: 1;
	-webkit-font-smoothing: antialiased;
	content: "f107";
	font-size: 80%;
	margin-right: 0.3em;
	opacity: 0.4;
	vertical-align: middle;
}

.cbp-hsmenu > li.cbp-hsitem-open > a:not(:only-child):before {
	content: "f106";
}

/* Add a triangle to currently open menu item link */
.cbp-hsmenu > li.cbp-hsitem-open > a:after {
	top: 100%;
	border: solid transparent;
	content: " ";
	height: 0;
	width: 0;
	position: absolute;
	pointer-events: none;
	border-color: transparent;
	border-top-color: #47a3da;
	border-width: 10px;
	left: 50%;
	margin-left: -10px;
}

/* Submenu style */
.cbp-hssubmenu {
	position: absolute;
	left: 0;
	top: 100%;
	width: 100%;
	z-index: 0;
	text-align: center; /* for aligning the sub items */
	visibility: hidden;
}

.cbp-hssubmenu:before, 
.cbp-hssubmenu:after { 
	content: " "; 
	display: table; 
}
.cbp-hssubmenu:after { 
	clear: both; 
}

/* Let's allow 6 item in a row */
.cbp-hssubmenu > li {
	width: 16.2%;
	display: inline-block;
	vertical-align: top;
	box-shadow: -28px 0 0 -27px #ddd, 0 -28px 0 -27px #ddd;
	opacity: 0;
	-webkit-transition: opacity 0.1s 0s;
	-moz-transition: opacity 0.1s 0s;
	transition: opacity 0.1s 0s;
}

/* First 6 items don't have upper box shadow */
.cbp-hssubmenu > li:nth-child(-n+6) {
	box-shadow: -28px 0 0 -27px #ddd;
} 

/* Every 7th item does not have a left box shadow */
.cbp-hssubmenu > li:nth-child(6n+1) {
	box-shadow:  0 -28px 0 -27px #ddd;
}

/* The first one does not have any box shadow */
.cbp-hssubmenu > li:first-child {
	box-shadow: none;
}

.cbp-hssubmenu > li a {
	display: block;
	text-align: center;
	color: #a2a2a2;
	outline: none;
	padding: 2em 1em 1em 1em;
}

.no-touch .cbp-hssubmenu > li a:hover,
.no-touch .cbp-hssubmenu > li a:focus {
	color: #888;
}

.cbp-hssubmenu > li a img {
	border: none;
	outline: none;
	display: inline-block;
	margin: 0;
	max-width: 100%;
	-webkit-transition: opacity 0.2s;
	-moz-transition: opacity 0.2s;
	transition: opacity 0.2s;
}

.no-touch .cbp-hssubmenu > li a:hover img {
	opacity: 0.5;
}

.cbp-hssubmenu > li a span {
	display: block;
	min-height: 3em;
	margin-top: 0.4em;
}

.cbp-hsmenu > li.cbp-hsitem-open .cbp-hssubmenu {
	z-index: 1000;
	visibility: visible;
}

.cbp-hsmenu > li.cbp-hsitem-open .cbp-hssubmenu > li {
	opacity: 1;
	-webkit-transition: opacity 0.5s 0.1s;
	-moz-transition: opacity 0.5s 0.1s;
	transition: opacity 0.5s 0.1s;
}

/* Helper div for animating the background */
.cbp-hsmenubg {
	background: #f7f7f7;
	position: absolute;
	width: 100%;
	top: 100%;
	left: 0;
	z-index: 0;
	height: 0px;
}

.no-touch .cbp-hsmenubg {
	-webkit-transition: height 0.3s;
	-moz-transition: height 0.3s;
	transition: height 0.3s;
}

@media screen and (max-width: 65em){
	.cbp-hsmenu-wrapper {
		font-size: 80%;
	}
}

@media screen and (max-width: 51.4375em){
	.cbp-hsmenu-wrapper {
		font-size: 100%;
	}

	.cbp-hsmenu-wrapper .cbp-hsmenu {
		padding: 0;
		max-width: none;
		width: 100%;
	}

	.cbp-hsmenu > li {
		border-top: 1px solid rgba(255,255,255,0.5);
		text-align: center;
		margin: 0 auto;
		display: block;
	}

	.cbp-hsmenu > li:first-child {
		border-top: none;
	}

	.cbp-hsmenu > li > a {
		display: block;
	}

	.cbp-hsmenu > li > a:not(:only-child):before {
		line-height: 1.8;
		right: 0;
		position: absolute;
		font-size: 200%;
	}

	.cbp-hsmenubg {
		display: none;
	}

	.cbp-hssubmenu {
		background: #f7f7f7;
		position: relative;
		overflow: hidden;
		height: 0;
	}

	.cbp-hsmenu > li.cbp-hsitem-open .cbp-hssubmenu {
		height: auto;
	}

	/* Let's only allow 3 item in a row now */
	.cbp-hssubmenu > li {
		width: 30%;
	}

	/* Reset box shadows for the 6 items in row case */
	.cbp-hssubmenu > li:nth-child(-n+6),
	.cbp-hssubmenu > li:nth-child(6n+1) {
		box-shadow: -28px 0 0 -27px #ddd, 0 -28px 0 -27px #ddd;
	}

	/* First 4 items don't have upper box shadow */
	.cbp-hssubmenu > li:nth-child(-n+3) {
		box-shadow: -28px 0 0 -27px #ddd;
	} 

	/* Every 5th item does not have a left box shadow */
	.cbp-hssubmenu > li:nth-child(3n+1) {
		box-shadow:  0 -28px 0 -27px #ddd;
	}

}

@media screen and (max-width: 25em){
	/* Let's only allow 1 item in a row now */
	.cbp-hssubmenu > li {
		width: 100%;
		display: block;
	}

	.cbp-hsmenu-wrapper .cbp-hssubmenu > li {
		box-shadow: 0 1px #cecece;
		text-align: left;
	}

	.cbp-hssubmenu > li a {
		text-align: left;
		line-height: 50px;
		padding: 0.4em 1em;
	}

	.cbp-hssubmenu > li a img {
		float: left;
		max-height: 50px;
	}

	.cbp-hssubmenu > li a span {
		min-height: 0;
		margin: 0;
	}
}

The JavaScript

/**
 * cbpHorizontalSlideOutMenu.js v1.0.0
 * http://www.codrops.com
 *
 * Licensed under the MIT license.
 * http://www.opensource.org/licenses/mit-license.php
 * 
 * Copyright 2013, Codrops
 * http://www.codrops.com
 */
;( function( window ) {
	
	'use strict';

	var document = window.document;

	function extend( a, b ) {
		for( var key in b ) { 
			if( b.hasOwnProperty( key ) ) {
				a[key] = b[key];
			}
		}
		return a;
	}

	function cbpHorizontalSlideOutMenu( el, options ) {	
		this.el = el;
		this.options = extend( this.defaults, options );
		this._init();
	}

	cbpHorizontalSlideOutMenu.prototype = {
		defaults : {},
		_init : function() {
			this.current = -1;
			this.touch = Modernizr.touch;
			this.menu = this.el.querySelector( '.cbp-hsmenu' );
			this.menuItems = this.el.querySelectorAll( '.cbp-hsmenu > li' );
			this.menuBg = document.createElement( 'div' );
			this.menuBg.className = 'cbp-hsmenubg';
			this.el.appendChild( this.menuBg );
			this._initEvents();
		},
		_openMenu : function( el, ev ) {
			
			var self = this,
				item = el.parentNode,
				items = Array.prototype.slice.call( this.menuItems ),
				submenu = item.querySelector( '.cbp-hssubmenu' ),
				closeCurrent = function( current ) {
					var current = current || self.menuItems[ self.current ];
					current.className = '';
					current.setAttribute( 'data-open', '' );	
				},
				closePanel = function() {
					self.current = -1;
					self.menuBg.style.height = '0px';
				};

			if( submenu ) {

				ev.preventDefault();

				if( item.getAttribute( 'data-open' ) === 'open' ) {
					closeCurrent( item );
					closePanel();
				}
				else {
					item.setAttribute( 'data-open', 'open' );
					if( self.current !== -1 ) {
						closeCurrent();
					}
					self.current = items.indexOf( item );
					item.className = 'cbp-hsitem-open';
					self.menuBg.style.height = submenu.offsetHeight + 'px';
				}
			}
			else {
				if( self.current !== -1 ) {
					closeCurrent();
					closePanel();
				}
			}

		},
		_initEvents : function() {
			
			var self = this;

			Array.prototype.slice.call( this.menuItems ).forEach( function( el, i ) {
				var trigger = el.querySelector( 'a' );
				if( self.touch ) {
					trigger.addEventListener( 'touchstart', function( ev ) { self._openMenu( this, ev ); } );
				}
				else {
					trigger.addEventListener( 'click', function( ev ) { self._openMenu( this, ev ); } );	
				}
			} );
			
			window.addEventListener( 'resize', function( ev ) { self._resizeHandler(); } );

		},
		// taken from https://github.com/desandro/vanilla-masonry/blob/master/masonry.js by David DeSandro
		// original debounce by John Hann
    	// http://unscriptable.com/index.php/2009/03/20/debouncing-javascript-methods/
		_resizeHandler : function() {
			var self = this;
			function delayed() {
				self._resize();
				self._resizeTimeout = null;
			}

			if ( this._resizeTimeout ) {
				clearTimeout( this._resizeTimeout );
			}

			this._resizeTimeout = setTimeout( delayed, 50 );
		},
		_resize : function() {
			if( this.current !== -1 ) {
				this.menuBg.style.height = this.menuItems[ this.current ].querySelector( '.cbp-hssubmenu' ).offsetHeight + 'px';
			}
		}
	}

	// add to global namespace
	window.cbpHorizontalSlideOutMenu = cbpHorizontalSlideOutMenu;

} )( window );

Tagged with:

Manoela Ilic

Manoela is the main tinkerer at Codrops. With a background in coding and passion for all things design, she creates web experiments and keeps frontend professionals informed about the latest trends.

Stay up to date with the latest web design and development news and relevant updates from Codrops.

Feedback 66

Comments are closed.
  1. Could this be modified to close on click outside nav?

    Well done guys. Another stellar blueprint.

  2. What a great menu!

    Apologies but I am a novice to JS. Can this be changed/edited so that the menu will trigger on a mouse over and close when the user moves the mouse away from the drop down?

    • I have run into the same issue. Any clues as how to as how to modify this menu for mouseover/enter would be greatly appreciated.

    • Thank you for the time you spent on this. You’re work is very practical and easy to implement. Thanks for sharing.

      I too am curious, in the js, what needs to be changed to have the behave that it opens upon mouseover and hides when the mouse leaves the active area? Not clicking or toggle per hover. Does anyone have any insight here? Thanks in advance for any consideration.

  3. Is this menu compatible with Internet Explorer 8? I am not referring to the Media Queries, just if it functions ok in full desktop size?

  4. Awesome. Is there a way to push down the content underneath when an item is clicked?

  5. For some reason this tutorial doesn’t seem to work in any browser. I’ve tried Firefox and Chrome and nothing happens.

  6. has anybody solved the problem for IE8 and IE9?

    the bug is in _initEvents function just in the call to Array.prototype.slice.call. the JS error is JScript object expected.

  7. I would like use it for a nav bar placed at bottom:0; of window
    It’s possible slide out to the opposite side: from down to up?
    Thank you 🙂

  8. Hello!
    Thanks for this wonderful tut, I’m a newbie in wed dev so I’ve combined it with your BackgroundSlideshow tutorial to create this http://www.akkornteon.gr/archive for my team (4 greek students building a tour guide app). I also borrowed the footer from the AnimatedContentMenu, where I gave a link back to codrops 😉

    I have 2 questions though: How do I prevent the menu from sliding up & down? How can I reduce the page size to fit exactly the screen?

    I’m also trying to use transparency to the black menu when in mobile screen sizes, but I’m getting solid black.

    Thank you,
    Yiannis

  9. Hi,

    unfortunatly it does not work in firefox (3.6). How can I fix it? Works perfectly in Safari….
    greetings

    Janina

  10. Hello,
    Please Tell me exactly what I have to do to resolve for IE 8 and IE 9 issue?
    Thanks

  11. It is sometimes difficult to scroll down the screen on mobile because it keeps expanding the nav items that have drop downs. Is it possible to make it on mobile so that only when you click the ARROW does it expand?
    Thanks

  12. Really nice!

    Finally , has anybody solved the problem for IE8 and IE9? 🙂

  13. If you wanted to place a div block right underneath the menu, how would you make it respond to the changing heights of the menu above it when you click on the menu options?
    Nice menu by the way!

  14. Figured it out, on top of adding “height: auto;” to my divs,

    I had to remove “position absolute” from the component.css file:
    /* Helper div for animating the background */
    .cbp-hsmenubg {
    background: #f7f7f7;
    position: absolute; <——Remove
    wid

    • Hey Kim K,
      Did you ever figure out how to implement a submenu in the unordered list like in the Shake Shack website?
      Any help would be appreciated
      Cheers

      Dan S

  15. Using the non-min js, IE8 error:

    JS Object expected
    Line 90
    Char 4

    Array.prototype.slice.call( this.menuItems ).forEach( function( el, i ) {

  16. Still i am getting error for ie8

    Array.prototype.slice: ‘this’ is not a JavaScript object

    what you guys are doing still?

  17. Yea, just tested this menu on a couple of browsers and it doesn’t work on android devices. Is there a fix? I’d love to use it. Basically the menu will open and it will show empty space. If one scrolls down the menu is located further down the page; an obvious bug. In fact, the demo from your site doesn’t work either (so I can rule out something silly in my code). Can anyone assist?

    Thanks so much!

    /tia

    • I’d happily pay for a javascript wizard to improve this – it’s absolutely perfect for the site I’m working on but I need a bullet proof solution down to ie8.

    • Fix for android device:

      @media screen and (max-width: 51.4375em){

      .cbp-hssubmenu {
      background: rgba(0, 0, 0, 0.7);
      /*position: relative;*/
      overflow: hidden;
      height: 0;
      }

  18. HI,

    This is just what I’m looking for but I need the first sub-menu to open when you first land on the site. Does anyone know how to make that happen?

    Any help much appreciated.

    • Are you using Jquery? You could try adding an id to the anchor that drops the menu and then use an onload event with something like:

      $(‘#anchor-id’).click();

  19. Hello everyone,

    is it possible to display two HORIZONTAL SLIDE OUT menus one under another ?

    How to do this to properly display the submenu menu?

    Please help me in solving this problem.

    Best regards,
    idejka

  20. Has anyone yet figured out how to have one submenu already open when the pages loads? Any help would be greatly appreciated. Thanks

  21. This is probably a stupid question but is there any way to enable the menu to push the content down below it rather than overlay? I’ve tried messing around in the inspector but it seems it needs the absolute positioning to push the menu wider than the LI element that it is contained within which makes sense. Just trying to see if there is a way.

  22. first of all thanks to share this great script.
    Any chance to close the submenu when the user click on it?
    thank you in advance
    Mark

    • fixed , it was so simple 😉

      cbpHorizontalSlideOutMenu.js
      ————————————————-
      cbpHorizontalSlideOutMenu.prototype = {
      defaults : {},
      _init : function() {

      this.menuItems = this.el.querySelectorAll( ‘.cbp-hsmenu > li, .cbp-hssubmenu > li’ );

      },

      }

  23. Anyone figured out how to fix the IE8 js error yet? I really need to launch a site but it doesn’t work in IE 🙁

  24. After doing some digging there are a few things killing this plugin in IE8 but the main part is IE8 doesn’t support the use of Array.prototype.slice.call (used in _initEvents and _openMenu) also IE8 doesn’t support addEventListener but that is easy enough to patch with attachEvent(). Anyone got any ideas about getting Array.prototype.slice.call to work in IE8??

  25. Hi, If anyone knows how to make this pushdown content and rollover work instead of click, please contact me @ 45hannah@gmail.com. I am willing to pay. It’s for a project I’m working on.

  26. I upgraded to Firefox 26.0 and now the menu does not work. Works fine in Chrome 31.0.xxxx. Crazy

  27. Hi,

    Menu is not collected when pulled out of the mouse.

    Mouse is clicked on the link in the menu will grow back or want to get top.

  28. Does anyone know how to add another drop down category to this? Mine won’t work

    • Hello Andrew,, i put this very cool menu in my website, have a look at the HTML source code to adding other drop down category (I make 5).
      All is working well for me on all browser.
      I want to thank you “Mary Lou” for this pretty menu.

  29. Hi, menu is Great!
    But I con not add button “Close” like “X”
    may be anybody can help me?

    Menu doesn’t work on IE11 on windows8

  30. This menu is great! Is there a way to have the submenu close when clicking on a submenu link? I am using anchors on my page, so I just want the submenu to collapse when the user clicks one of the links.

  31. This slide down menu is awesome. But I am facing an issue on reducing the size of the browser. The slide out menu does not show up. Is there a solution known to anyone for this ??
    Thanks a ton

  32. Hello, i have over 100 Sub Categories and the browser Window use the height from the Submenu and show me a scrollbar (The Submenu is not opened, how can i change it to show the scrollbar when i open the submenu?

  33. Hi, I love this work!!
    How I can have an active tab? Load a page and have a open tab.

  34. Hi, does anyone know how to change the ‘click’ on the main menu list item to a ‘hover’?

    Thanks