Code:
Today I want to show you how to reconstruct the sidebar menu of the Google Nexus 7 page. It slides out with a really nice effect where some subitems get expanded as well. When hovering over a special menu icon, the sidebar icons will be revealed. When clicking on the icon, the whole sidebar menu will be shown. The first sidebar menu item is a search input which is styled the same way like the other menu items.
We will reconstruct this menu using unordered, nested lists and some CSS transitions. We’ll use JavaScript to apply classes for the opening effects and handling the hover and click events. With the help of a media query, we’ll adjust the size to make sense for smaller devices.
So let’s get started!
THE MARKUP
Our menu will consist of two main parts: a main menu, the one that you can see at the top like a header, and the sidebar menu. We’ll give the class “gn-menu-main” to the first one and wrap the second one in a nav element. You could of course use any structure that you prefer.
The first menu item will contain the menu icon anchor and the nav element:
<ul id="gn-menu" class="gn-menu-main">
<li class="gn-trigger">
<a class="gn-icon gn-icon-menu"><span>Menu</span></a>
<nav class="gn-menu-wrapper">
<!-- ... -->
</nav>
</li>
<li><a href="http://tympanus.net/codrops">Codrops</a></li>
<li><!-- ... --></li>
<!-- ... -->
</ul>
Inside of the nav element we’ll add another wrapper that will help us with hiding the nasty scrollbar for Windows browsers. The heart of this submenu is the unordered list with the class “gn-menu”. It will consist of list items, some of which will have a sublist. The first item will be the special search input:
<div class="gn-scroller">
<ul class="gn-menu">
<li class="gn-search-item">
<input placeholder="Search" type="search" class="gn-search">
<a class="gn-icon gn-icon-search"><span>Search</span></a>
</li>
<li>
<a class="gn-icon gn-icon-download">Downloads</a>
<ul class="gn-submenu">
<li><a class="gn-icon gn-icon-illustrator">Vector Illustrations</a></li>
<li><a class="gn-icon gn-icon-photoshop">Photoshop files</a></li>
</ul>
</li>
<li><a class="gn-icon gn-icon-cog">Settings</a></li>
<li><!-- ... --></li>
<!-- ... -->
</ul>
</div><!-- /gn-scroller -->
Now, let’s style everything.
THE CSS
Note that the CSS will not contain any vendor prefixes, but you will find them in the files.
Let’s start by setting border-box for all the box-sizing:
*,
*:after,
*::before {
box-sizing: border-box;
}
Since we’ll be using an icon font for the icons, we’ll head over to IcoMoon and select some nice icons from Matthew Skiles’ Eco Ico set.
@font-face {
font-weight: normal;
font-style: normal;
font-family: 'ecoicons';
src: url("../fonts/ecoicons/ecoicons.eot");
src: url("../fonts/ecoicons/ecoicons.eot?#iefix") format("embedded-opentype"), url("../fonts/ecoicons/ecoicons.woff") format("woff"), url("../fonts/ecoicons/ecoicons.ttf") format("truetype"), url("../fonts/ecoicons/ecoicons.svg#ecoicons") format("svg");
}
Later we’ll use a pseudo element to add the icons to the anchors.
But let’s style all the lists before:
.gn-menu-main,
.gn-menu-main ul {
margin: 0;
padding: 0;
background: white;
color: #5f6f81;
list-style: none;
text-transform: none;
font-weight: 300;
font-family: 'Lato', Arial, sans-serif;
line-height: 60px;
}
These are some general (reset) styles for the lists and the sublists.
Now, let’s specify the styles for the main list. It will be fixed to the top of the page and we’ll give it a height of 60 pixels:
.gn-menu-main {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 60px;
font-size: 13px;
}
The general style for all the links in our menu and submenus will be the following:
.gn-menu-main a {
display: block;
height: 100%;
color: #5f6f81;
text-decoration: none;
cursor: pointer;
}
Let’s also define some hover styles where we invert the colors. For the first sidebar menu item which will have a search input, we’ll need a special hover style. There we won’t have an anchor that fills all the item, so let’s define the hover on the li and control what happens to the icon (the anchor) and the li itself:
.no-touch .gn-menu-main a:hover,
.no-touch .gn-menu li.gn-search-item:hover,
.no-touch .gn-menu li.gn-search-item:hover a {
background: #5f6f81;
color: white;
}
The list item childen will float left and they’ll have a right border:
.gn-menu-main > li {
display: block;
float: left;
height: 100%;
border-right: 1px solid #c6d0da;
text-align: center;
}
The first list item will be the special trigger item and because we will hide the text and use a pseudo element for the menu icon, we will set the user-select to none and the width to be the same as the items’ height.
1
2
3
4
5
.gn-menu-main li.gn-trigger {
position: relative;
width: 60px;
user-select: none;
}
The last item in our main list will be floated right and we’ll swap the border:
1
2
3
4
5
.gn-menu-main > li:last-child {
float: right;
border-right: none;
border-left: 1px solid #c6d0da;
}
The anchors for the main menu will have some padding and we’ll style the text a bit differently:
1
2
3
4
5
6
.gn-menu-main > li > a {
padding: 0 30px;
text-transform: uppercase;
letter-spacing: 1px;
font-weight: bold;
}
Let’s clear the floats with the following micro clearfix hack by Nicolas Gallagher:
1
2
3
4
5
.gn-menu-main:after {
display: table;
clear: both;
content: '';
}
Alright, so now it just misses the style of the menu icon, but let’s leave that one for later when we define the other icon pseudo classes.
Let’s move on to the wrapper for the sidebar menu. Why do we need those extra wrappers? Well, if you don’t mind having a scrollbar visible you might as well get rid of them and simply set the menu to overflow-y: scroll. But since the scrollbar really breaks our minimal design in browsers on Windows, we’ll use a little trick to hide it. We’ll set the main wrapper to be overflow hidden, with a certain width (initially it’s just wide enough to see the icon bar). Then we’ll give the scroll wrapper a slightly larger width and a height of 100%. The scrollbar will be hidden. Our menu will then extend to the height it needs and it will be scrollable.
Initially we want to hide the menu, so we’ll give it a negative left value (of its width). Why are we not using 2D translate here?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
.gn-menu-wrapper {
position: fixed;
top: 60px;
bottom: 0;
left: 0;
overflow: hidden;
width: 60px; /* will be transitioned to 340px */
border-top: 1px solid #c6d0da;
background: white;
transform: translateX(-60px); /* will be transitioned to 0px */
transition: transform 0.3s, width 0.3s;
}
.gn-scroller {
position: absolute;
overflow-y: scroll;
width: 370px;
height: 100%;
}
.gn-menu {
border-bottom: 1px solid #c6d0da;
text-align: left;
font-size: 18px;
}
Let’s add a box shadow for separating the list items. This will help us avoid double lines when hiding the submenu items:
1
2
3
4
.gn-menu li:not(:first-child),
.gn-menu li li {
box-shadow: inset 0 1px #c6d0da
}
Let’s add a transition the the submenu list items and set their initial height to 0:
1
2
3
4
5
.gn-submenu li {
overflow: hidden;
height: 0;
transition: height 0.3s;
}
The color will be slightly lighter than the parent menu items:
1
2
3
.gn-submenu li a {
color: #c1c9d1
}
Now, let’s style the special search item and the search input specifically. We want to make it really subtle like on the Google Nexus page, so we’ll give it a transparent background colors and make the placeholders look like a normal menu item:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
input.gn-search {
position: relative;
z-index: 10;
padding-left: 60px;
outline: none;
border: none;
background: transparent;
color: #5f6f81;
font-weight: 300;
font-family: 'Lato', Arial, sans-serif;
cursor: pointer;
}
/* placeholder */
.gn-search::-webkit-input-placeholder {
color: #5f6f81
}
.gn-search:-moz-placeholder {
color: #5f6f81
}
.gn-search::-moz-placeholder {
color: #5f6f81
}
.gn-search:-ms-input-placeholder {
color: #5f6f81
}
Most browsers will hide the placeholder when we click on the input which is much better for the user to understand that this is an input. Chrome does not have that behavior so we’ll use a little trick to emulate the same thing by setting the color of the placeholder to transparent once the user clicks on the input and focuses it:
1
2
3
4
5
6
7
8
.gn-search:focus::-webkit-input-placeholder,
.no-touch .gn-menu li.gn-search-item:hover .gn-search:focus::-webkit-input-placeholder {
color: transparent
}
input.gn-search:focus {
cursor: text
}
On hover we will change the color of the input text to white, just like we do to the other anchors (this is the text the user types):
1
2
3
.no-touch .gn-menu li.gn-search-item:hover input.gn-search {
color: white
}
We will also do that for the placeholder text:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/* placeholder */
.no-touch .gn-menu li.gn-search-item:hover .gn-search::-webkit-input-placeholder {
color: white
}
.no-touch .gn-menu li.gn-search-item:hover .gn-search:-moz-placeholder {
color: white
}
.no-touch .gn-menu li.gn-search-item:hover .gn-search::-moz-placeholder {
color: white
}
.no-touch .gn-menu li.gn-search-item:hover .gn-search:-ms-input-placeholder {
color: white
}
The search icon anchor will be a special one because it won’t have the text visible next to it. The whole list item is a trick box. You see, by setting the icon anchor to position absolute, we will let the search input start at the very left of our list item. But remember, we gave the input a large left padding which will make the text start only after out search icon. When clicking on the search icon we will be actually clicking on the input, focusing it.
1
2
3
4
5
6
.gn-menu-main a.gn-icon-search {
position: absolute;
top: 0;
left: 0;
height: 60px;
}
Now, let’s style the ::before pseudo element for the icons. We’ll set them to inline-block and give them a width of 60 pixel. We have to reset all the font styles, because now we’ll be using our icon font that we’ve included in the beginning of the CSS:
1
2
3
4
5
6
7
8
9
10
11
12
13
.gn-icon::before {
display: inline-block;
width: 60px;
text-align: center;
text-transform: none;
font-weight: normal;
font-style: normal;
font-variant: normal;
font-family: 'ecoicons';
line-height: 1;
speak: none;
-webkit-font-smoothing: antialiased;
}
Let’s define the content for all the icons:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
.gn-icon-help::before {
content: "\e000"
}
.gn-icon-cog::before {
content: "\e006"
}
.gn-icon-search::before {
content: "\e005"
}
.gn-icon-download::before {
content: "\e007"
}
.gn-icon-photoshop::before {
content: "\e001"
}
.gn-icon-illustrator::before {
content: "\e002"
}
.gn-icon-archive::before {
content: "\e00d"
}
.gn-icon-article::before {
content: "\e003"
}
.gn-icon-pictures::before {
content: "\e008"
}
.gn-icon-videos::before {
content: "\e009"
}
Normally, we want the text of the anchor to show next to the icon but sometimes, we only want to show the icon. But we don’t just want an empty anchor, the text should still be in the HTML. So we will wrap those special cases into a span which we will simply hide by setting the width and height to 0 and the overflow to hidden. Why not simply using display: none? Hiding the content like that would make it inaccessible to screen readers, so let’s make sure that we don’t “erase” anything so important for them:
1
2
3
4
5
6
.gn-icon span {
width: 0;
height: 0;
display: block;
overflow: hidden;
}
Let’s not forget about our little menu icon in the main menu. So, we won’t use an icon from the icon font here, although you of course could. Instead we’ll create it with a box shadow that will use alternating colors (background and blue) to create the three lines. You could also use a gradient here if you prefer.
1
2
3
4
5
6
7
8
9
.gn-icon-menu::before {
margin-left: -15px;
vertical-align: -2px;
width: 30px;
height: 3px;
background: #5f6f81;
box-shadow: 0 3px white, 0 -6px #5f6f81, 0 -9px white, 0 -12px #5f6f81;
content: '';
}
On hover, we will invert the box shadow colors:
1
2
3
4
5
.no-touch .gn-icon-menu:hover::before,
.no-touch .gn-icon-menu.gn-selected:hover::before {
background: white;
box-shadow: 0 3px #5f6f81, 0 -6px white, 0 -9px #5f6f81, 0 -12px white;
}
And when it’s selected (our side menu is open), we’ll make it more blue:
1
2
3
4
.gn-icon-menu.gn-selected::before {
background: #5993cd;
box-shadow: 0 3px white, 0 -6px #5993cd, 0 -9px white, 0 -12px #5993cd;
}
The last thing we need to do is to define our two classes for opening the menu for showing the icons only and for showing the whole menu. When we hover over the menu icon, we will show the icons only. Let’s call this class gn-open-part. The other class, gn-open-all will be applied either if we click on the main menu icon or if we hover over the icon part shown (the menu sidebar menu itself).
In both cases, we’ll need to reset the translate to 0:
1
2
3
4
.gn-menu-wrapper.gn-open-all,
.gn-menu-wrapper.gn-open-part {
transform: translateX(0px);
}
If we want to open the whole menu, we’ll need to set the right width:
1
2
3
.gn-menu-wrapper.gn-open-all {
width: 340px;
}
Opening the whole menu, should also expand the submenu items:
1
2
3
.gn-menu-wrapper.gn-open-all .gn-submenu li {
height: 60px;
}
Last, but not least, our precious media query that will make the menu use the whole width of the screen:
1
2
3
4
5
6
7
8
9
10
@media screen and (max-width: 422px) {
.gn-menu-wrapper.gn-open-all {
transform: translateX(0px);
width: 100%;
}
.gn-menu-wrapper.gn-open-all .gn-scroller {
width: 130%;
}
}
And we’ll also adjust the width of the scroll wrapper to be larger than the 100%. This is probably not too important as we don’t see scrollbars on most devices of that size.
Alright, now that we’ve styled everything, we’ll use some JavaScript for the logic of opening and closing the menu (i.e. applying the classes).
THE JAVASCRIPT
So let’s create a small script that will take care of the menu functionality. When we hover over the menu icon, we want the first part of the menu to slide out so that we can see the icons. If we hover over the sidebar menu area or if we click on the main menu icon, then the rest of the menu should slide out. Clicking the menu icon again or clicking on any other part of the body should make the whole menu slide back in. So let’s see how we can pull all that off.
We start by caching some elements and initializing some variables. The bodyClickFn function defines what happens when the menu is open and we click somewhere else on the document. We should also take care of touch events.
1
2
3
4
5
6
7
8
9
10
11
12
13
_init : function() {
this.trigger = this.el.querySelector( 'a.gn-icon-menu' );
this.menu = this.el.querySelector( 'nav.gn-menu-wrapper' );
this.isMenuOpen = false;
this.eventtype = mobilecheck() ? 'touchstart' : 'click';
this._initEvents();
var self = this;
this.bodyClickFn = function() {
self._closeMenu();
this.removeEventListener( self.eventtype, self.bodyClickFn );
};
}
Let’s take a look at the events that need to be initialized.
We want to open the first part of the menu (let’s call it icon menu) when the main menu icon (trigger) is hovered. When we move the mouse out this same menu should slide back in.
1
2
this.trigger.addEventListener( 'mouseover', function(ev) { self._openIconMenu(); } );
this.trigger.addEventListener( 'mouseout', function(ev) { self._closeIconMenu(); } );
Once the icon menu is in the viewport, hovering over it will make the rest of the menu slide out. After it slides out, and we click somewhere on the body then the menu should slide back in. We need to bind the respective event (click or touchstart) to the document.
1
2
3
4
this.menu.addEventListener( 'mouseover', function(ev) {
self._openMenu();
document.addEventListener( self.eventtype, self.bodyClickFn );
} );
Finally if we click the menu icon, we want the whole menu to slide out or slide in if it’s already in the viewport. We will also bind (or unbind) the respective event (click or touchstart) to the document.
1
2
3
4
5
6
7
8
9
10
11
12
this.trigger.addEventListener( this.eventtype, function( ev ) {
ev.stopPropagation();
ev.preventDefault();
if( self.isMenuOpen ) {
self._closeMenu();
document.removeEventListener( self.eventtype, self.bodyClickFn );
}
else {
self._openMenu();
document.addEventListener( self.eventtype, self.bodyClickFn );
}
} );
One last thing: we don’t want the menu to slide back in if we click somewhere inside the menu area. Since we are binding the click/touchstart event to the document (so that the menu closes) we need to do the following:
1
this.menu.addEventListener( this.eventtype, function(ev) { ev.stopPropagation(); } );
And here is the final _initEvents function and the methods to open and close the menu.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
_initEvents : function() {
var self = this;
if( !mobilecheck() ) {
this.trigger.addEventListener( 'mouseover', function(ev) { self._openIconMenu(); } );
this.trigger.addEventListener( 'mouseout', function(ev) { self._closeIconMenu(); } );
this.menu.addEventListener( 'mouseover', function(ev) {
self._openMenu();
document.addEventListener( self.eventtype, self.bodyClickFn );
} );
}
this.trigger.addEventListener( this.eventtype, function( ev ) {
ev.stopPropagation();
ev.preventDefault();
if( self.isMenuOpen ) {
self._closeMenu();
document.removeEventListener( self.eventtype, self.bodyClickFn );
}
else {
self._openMenu();
document.addEventListener( self.eventtype, self.bodyClickFn );
}
} );
this.menu.addEventListener( this.eventtype, function(ev) { ev.stopPropagation(); } );
},
_openIconMenu : function() {
classie.add( this.menu, 'gn-open-part' );
},
_closeIconMenu : function() {
classie.remove( this.menu, 'gn-open-part' );
},
_openMenu : function() {
if( this.isMenuOpen ) return;
classie.add( this.trigger, 'gn-selected' );
this.isMenuOpen = true;
classie.add( this.menu, 'gn-open-all' );
this._closeIconMenu();
},
_closeMenu : function() {
if( !this.isMenuOpen ) return;
classie.remove( this.trigger, 'gn-selected' );
this.isMenuOpen = false;
classie.remove( this.menu, 'gn-open-all' );
this._closeIconMenu();
}
And that’s all! Thank you for reading and I hope you enjoyed this tutorial and find it useful!