A pure CSS hoverable dropdown menu is easy, but an onclick menu usually requires JavaScript. In this article, I’m going to share a clickable dropdown menu made with pure CSS. The sample code is put on CodePen: http://codepen.io/arthurtw/pen/KMarNG
My approach is based on Koen Kivits’s blog post: A pure CSS onclick menu. His solution is great, but the iOS Safari support was missing. I will explain how to deal with iOS issues later.
All right, let’s start.
From hover to clickable
It’s easy to detect menu hover with a CSS :hover
selector. How about detecting menu onclick? It’s not difficult, too. We can use a :focus
selector. Say we have the following HTML menu:
<div class="dropdown-menu">
<span tabindex="0">Click Me!</span>
<ul>
<li><a href="#" onclick="alert('click 1')">Item 1</a></li>
<li><a href="#" onclick="alert('click 2')">Item 2</a></li>
<li><a href="#" onclick="alert('click 3')">Last Item</a></li>
</ul>
</div>
The ul
element is hidden, unless the span
element (the menu label “Click Me!”) is clicked and hence receives focus. Do you notice the attribute tabindex="0"
? It’s for the span
element to receive focus.
Our CSS looks like this:
.dropdown-menu {
position: relative;
}
.dropdown-menu > ul {
position: absolute;
z-index: 1;
display: none;
}
.dropdown-menu > span:focus ~ ul {
display: block;
}
In the CSS code above, the ul
element has display: none
attribute by default, but when it’s preceded by an span:focus
element, it becomes display: block
. This is the basis of a clickable dropdown menu.
However, it has several issues. Let’s fix them one by one.
1. Menu items are not clickable
The above CSS menu is unusable. It can be opened by clicking the menu label, but the menu item will never receive a click
event. Why? Because when you click a menu item, say, “Item 1”, the focus goes to its <a href=...>
element and the span
element loses focus immediately. Because the span:focus
CSS rule (i.e. .dropdown-menu > span:focus ~ ul
) does not apply, the ul
element disappears (i.e. display: none
applies).
Here we have at least two solutions:
- Use CSS
visibility
instead ofdisplay
, and use a half-second CSStransition
for the browser to trigger aclick
event within a half second. Drawback: IE9 does not support CSStransition
. - Add a
ul:hover
CSS rule to keep theul
element visible even when we losespan:focus
. Drawback: the menu item won’t disappear until the cursor leaves the menu area, so the user experience is less ideal.
In the blog post, we’ll use solution #1. If you want to support IE9, you may change the CSS rule from .dropdown-menu > span:focus ~ ul { ... }
to .dropdown-menu > span:focus ~ ul, .dropdown-menu > ul:hover { ... }
(i.e. add an additional .dropdown-menu > ul:hover
selector).
Here is the modified CSS using solution #1:
.dropdown-menu > ul {
position: absolute;
z-index: 1;
visibility: hidden;
transition: visibility 0.5s;
}
.dropdown-menu > span:focus ~ ul {
visibility: visible;
}
2. The half-second delay
The solution results in a half-second lag before the menu item disappears. We can use the opacity: 0
attribute to make the menu item “disappear” (actually, transparent) immediately:
.dropdown-menu > ul {
position: absolute;
z-index: 1;
visibility: hidden;
transition: visibility 0.5s;
opacity: 0;
}
.dropdown-menu > span:focus ~ ul {
visibility: visible;
opacity: 1;
}
3. The iOS Safari issue
The CSS menu does not work on an iOS device, because by default, mobile Safari does not consider span
elements clickable. The solution I found is to add onclick="return true"
to the span
element, so it becomes clickable on iOS Safari:
<span tabindex="0" onclick="return true">Click Me!</span>
But the iOS challenge has not finished. The next issue is related to iOS, too.
4. The second click does not close the menu
To users, the first click on the menu label (“Click Me!”) opens the menu, and the second click should close it. Our CSS menu does not work as expected, because the focus remains on the menu label after the second click.
Koen Kivits’s solution is using the pointer-events
attribute to control whether the element receives mouse click event. When the span
element receives focus, its pointer-events
attribute is set to none
, and the pointer-events
attribute of its parent element (<div class="dropdown-menu">
) is set to auto
.
However, it does not work on iOS Safari. For iOS Safari to consider it clickable, the element must have both the pointer-events: auto
attribute and the onclick
event handler. We cannot attach/detach event handlers for HTML elements using CSS.
My solution is to overlay an empty div
element. It’s hidden by default, and shown when the span
element receives focus. Because it sits on top of the span
element, the second click on the menu label actually clicks on the div
element, hence 1) the focus goes to div
, 2) span:focus
is lost, 3) menu items are hidden, and 4) the div
element is hidden again.
The div
element must have tabindex="0"
and onclick="return true"
attributes, too. Here is the modified HTML:
<div class="dropdown-menu">
<span tabindex="0" onclick="return true">Click Me!</span>
<div tabindex="0" onclick="return true"></div>
<ul>
<li><a href="#" onclick="sampleMenu(this)">Item 1</a></li>
<li><a href="#" onclick="sampleMenu(this)">Item 2</a></li>
<li><a href="#" onclick="sampleMenu(this)">Last Item</a></li>
</ul>
</div>
We set top: 0; left: 0; right: 0; bottom: 0;
attributes to the div
element, so it becomes as large as its parent. Here is the modified CSS:
.dropdown-menu > div {
background-color: rgba(0, 0, 0, 0);
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: none;
}
.dropdown-menu > span:focus ~ div {
display: block;
}
5. Aesthetic issues
So far, we have fixed all usability issues. There are several aesthetic issues:
- An outline shows up when the menu label (and the empty
div
element) receives focus. - Double-clicking the menu label causes some text highlighted.
- On iOS Safari, a background grey box flashes when the menu label is clicked.
To fix issue #1, we can set CSS outline
attribute to 0
. To fix issue #2, we can set CSS user-select
attribute to none
. To fix issue #3, we can set CSS -webkit-tap-highlight-color
attribute to a transparent color. Here is the modified CSS:
.dropdown-menu > span {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.dropdown-menu > span,
.dropdown-menu > div {
cursor: pointer;
outline: 0;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
6. Clicking blank area on iOS does not close the menu
There is one more issue on iOS Safari: Clicking blank area does not close the menu, because the blank area cannot receive focus. We may wrap a clickable div
element at the outmost to fix it:
<div style="-webkit-tap-highlight-color:rgba(0,0,0,0)" onclick="return true">
...
</div>
This adds a redundant div
element for non-iOS browsers, though.
7. FastClick compatibility
If you use the FastClick library (a tool to fix the click delay on mobile browsers), you need to add a CSS class needsclick
to the span
element:
<span tabindex="0" onclick="return true" class="needsclick">Click Me!</span>
On our site Twincl.com, we use solution #2 (ul:hover
) to support IE9. We use Fastclick, too.
A pure CSS onclick menu using checkbox
Another pure CSS solution for onclick menu is using <input type="checkbox" id="item-x">
and <label for="item-x">
. Here is a solution made by Felipe Fialho: http://www.felipefialho.com/css-components/#component-dropdown
Because a checkbox cannot be unchecked when user clicks blank area, Felipe Fialho uses a fullscreen overlay label
to fix the issue. However, it causes a new issue: the overlay label
absorbs mouse click event. I made a CodePen to demonstrate the issue: http://codepen.io/arthurtw/pen/Vjpmwq
In the CodePen example, the bottom link works fine when the dropdown menu is closed, but does not work when the menu is open.
That’s it. Hope this article gives you some interesting idea! Let me know if you have any comment.
This article convinced me to use Javascript for menu clicking. CSS solutions look very messy.
You are right. :-) In terms of onclick dropdown menu, the CSS solution is not clean.