A short tutorial to do responsive/mobile navigation toggle with only CSS (no javascript/jquery needed).
I do understand, to disable javascript in browser is not common anymore, and more website rely on javascript to work properly. But it’s interesting subject isn’t it? We can use this as a fallback if javascript is disabled.
HTML
the navigation html is very simple and basic navigation in WordPress.
<nav id="menu-primary"> <div class="menu-container"> <div id="menu-toggle-primary"> <a class="open-menu-primary" href="#menu-primary"><span>Open this menu</span></a> <a class="close-menu-primary" href="#"><span>Close this menu</span></a> </div><!-- .menu-toggle --> <div class="wrap"> <ul class="menu-items" id="menu-primary-items"> <li class="menu-item"><a href="#">Menu 1</a></li> <li class="menu-item"><a href="#">Menu 2</a></li> <li class="menu-item"><a href="#">Menu 3</a> <ul class="sub-menu"> <li class="menu-item"><a href="#">Menu 3.1</a></li> <li class="menu-item"><a href="#">Menu 3.2</a></li> </ul> </li> <li class="menu-item"><a href="#">Menu 4</a></li> </ul> </div><!-- .wrap --> </div><!-- .menu-container --> </nav>
as you can see, before menu items, we create a div for navigation toggle, and inside the div we have 2 link, one for opening the menu, and one for closing the menu.
<div id="menu-toggle-primary"> <a class="open-menu-primary" href="#menu-primary"><span>Open this menu</span></a> <a class="close-menu-primary" href="#"><span>Close this menu</span></a> </div><!-- .menu-toggle -->
CSS (before toggle, closed state)
Default css is simple, we just need to hide our menu ul#menu-primary-items
and hide the link to close the navigation because it’s already closed (hidden).
/* Hide Menu */ #menu-primary-items{ display: none; } #menu-toggle-primary a.close-menu-primary{ display: none; }
CSS (toggle open)
As you can see in the demo/html source, in the link to open the toggle linked to #menu-primary
element. So when we click the link, the browser will go to http://yourpageurl/#menu-primary
.
Now the interesting part, we can actually style the targeted element using CSS :target selector.
URLs with an # followed by an anchor name, link to a certain element within a document. The element being linked to is the target element.
The
:target
selector can be used to style the current active target element.
So, if we add this CSS it will only do stuff for selected element when it’s active (targeted using #element in url).
#element:target{ /* do stuff */ }
we are going to use :target
CSS selector and we can do something like this:
/* Display Menu Items */ #menu-primary:target #menu-primary-items{ display: block; } /* Hide Open Toggle Link */ #menu-primary:target #menu-toggle-primary a.open-menu-primary{ display: none; } /* Show Close Toggle Link */ #menu-primary:target #menu-toggle-primary a.close-menu-primary{ display: block; }
At this state, the menu items will be displayed, and the link to open the menu will be hidden.
FAQ
Using only CSS will make the page “jump”
It’s unavoidable, using javascript/jquery we can prevent this using event.preventDefault(). in the demo I add the navigation at the top of the page to prevent the “jumping” experience. If you create the navigation toggle in other position (for example, after the header), you can prevent/make it less jumpy by linking the to the right/nearest div/element id.
Browser Support
:target
CSS is supported by all major browser, but not supported in Internet Explorer 8, and earlier versions.
I hope this simple explanation is enough 🙂
If you have any question feel free to ask in the comment.
This is epic, thanks for sharing David 🙂
Hi,
thanks for a great tuturial. I have one question though. How do I make the navigation fixed? When I add ‘position:fixed’ to the navigation, I can’t scroll on my mobile.
Thanks.
Tanja
maybe you can try:
This is great, is there a way of making the list horizontal instead? so it would open across the top?
Hi Shellcreeper, I like your tutorial very much and I am trying to addapt it to my template, but I have one problem that is when I move the open & close buttons to a other div I can’t get them to target the menu anymore.
Here is a Fiddle of what I am trying to do http://jsfiddle.net/SanMoll/k8ej4fmo/2/
Hope you can give some advice, thanks
Very useful implementation, but I have an observation.
Probably has been fixed but here goes:
If you open and close the menu, say 7 times, it ‘ll take you equal amount of times pressing the BACK browser button just to go to the previous website.
Thank you so much for this tutorial! I like it because, it’s so easy & without JS. Even though one question. On my Side if I open the menu, it jumps to top. I tried with “position:fixed;” and “onclick=return false;” No chance! Do you have any Idea, how can I fix the problem?
Thanks again
Tonguc
If you want to use animation, try this code:
@import url(https://fonts.googleapis.com/css?family=Open+Sans);
$bg-color: #537ec5;
$text-color: #010038;
$inactive-item-color: #537ec5;
$active-item-color: #010038;
.fa-2x {
font-size: 26px;
}
.menu-container {
width: 500px;
height: 80px;
padding: 4px 20px 0px 20px;
background: #fff;
border-radius: 0 0 30px 30px;
position: relative;
z-index: 0;
}
.menu {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
display: flex;
position: relative;
}
input[type=”radio”] {
display: none;
}
label {
color: $inactive-item-color;
cursor: pointer;
z-index: 1;
display: flex;
align-items: center;
justify-content: center;
width: 25%;
transition: 0.25s ease-in;
}
.item {
text-align: center;
}
.item-title {
margin-top: 14px;
margin-bottom: -6px;
color: $text-color;
font-weight: 600;
font-size: 11px;
}
.blob-container {
position: absolute;
z-index: 0;
width: 25%;
transition: transform 0.2s ease-out;
}
.blob {
background: $active-item-color;
width: 50px;
height: 50px;
border-radius: 25px;
margin: 2px auto;
}
input[type=”radio”] {
&:checked {
& + label {
color: #fff;
}
}
}
input[id=”radio-0″] {
&:checked {
~ .blob-container {
transform: translateX(0%);
.blob {
animation: scaleY-0 .2s linear;
}
}
}
}
input[id=”radio-1″] {
&:checked {
~ .blob-container {
transform: translateX(100%);
.blob {
animation: scaleY-1 .2s linear;
}
}
}
}
input[id=”radio-2″] {
&:checked {
~ .blob-container {
transform: translateX(200%);
.blob {
animation: scaleY-2 .2s linear;
}
}
}
}
input[id=”radio-3″] {
&:checked {
~ .blob-container {
transform: translateX(300%);
.blob {
animation: scaleY-3 .2s linear;
}
}
}
}
@keyframes scaleY-0 {
0% { transform: scale(0);}
60% { transform: scaleY(0.5) translateY(30px); }
100% { transform: scaleY(1);}
}
@keyframes scaleY-1 {
0% { transform: scale(0);}
60% { transform: scaleY(0.5) translateY(-30px); }
100% { transform: scaleY(1);}
}
@keyframes scaleY-2 {
0% { transform: scale(0); }
60% { transform: scaleY(0.5) translateY(30px); }
100% { transform: scaleY(1); }
}
@keyframes scaleY-3 {
0% { transform: scale(0); }
60% { transform: scaleY(0.5) translateY(-30px); }
100% { transform: scaleY(1); }
}
body {
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
background: $bg-color;
font-family: ‘Open Sans’;
}