A pure CSS onclick menu with iOS support

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:

  1. Use CSS visibility instead of display, and use a half-second CSS transition for the browser to trigger a click event within a half second. Drawback: IE9 does not support CSS transition.
  2. Add a ul:hover CSS rule to keep the ul element visible even when we lose span: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:

  1. An outline shows up when the menu label (and the empty div element) receives focus.
  2. Double-clicking the menu label causes some text highlighted.
  3. 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.

2 comments 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.