Make CSS drop-down menus work on touch devices

This post is more than 12 years old.

CSS drop-down menus are very popular on sites with a hierarchy of pages. They let you get to where you want to go without having to navigate the pages in that hierarchy. But pure-CSS menus suffer a problem: touch devices often can’t show the drop-down, because they don’t have “hover” and clicking on the top level link goes there. This snippet offers a way around that.

On modern touch devices like iPhones, iPads and Android phones and tablets, there is a series of events associated with a tap. You get a “touchstart” when you first tap, then a “touchend” when you lift your finger, and finally a “click” to simulate a mouse click. (There are more; see the W3C’s touch events candidate recommendation and Mozilla’s touch events page.)

What this snippet does is keep track of the first time you tap on a menu link, and suppress the click event for that tap. On the second tap, it lets the click event occur. This lets your website visitor tap to expand the drop-down menu, then tap again if they want to go to that link.

In this snippet, my menu has the id “menu” and menu items with children have the class “children”. Adjust the call to querySelectAll to fit your menu.

[edit: Chrome 17 just came out and, naturally, the code I had here gave a false positive so I’ve updated the touch test.]
[edit: because iOS (iPads, iPhones) now do this automatically since iOS 5, you need to be able to disable this script on those devices. I’ve augmented the script below to do that.]

// see whether device supports touch events (a bit simplistic, but...)
var hasTouch = ("ontouchstart" in window);
var iOS5 = /iPad|iPod|iPhone/.test(navigator.platform) && "matchMedia" in window;

// hook touch events for drop-down menus
// NB: if has touch events, then has standards event handling too
// but we don't want to run this code on iOS5+
if (hasTouch && document.querySelectorAll && !iOS5) {
    var i, len, element,
        dropdowns = document.querySelectorAll("#menu li.children > a");

    function menuTouch(event) {
        // toggle flag for preventing click for this link
        var i, len, noclick = !(this.dataNoclick);

        // reset flag on all links
        for (i = 0, len = dropdowns.length; i < len; ++i) {
            dropdowns[i].dataNoclick = false;
        }

        // set new flag value and focus on dropdown menu
        this.dataNoclick = noclick;
        this.focus();
    }

    function menuClick(event) {
        // if click isn't wanted, prevent it
        if (this.dataNoclick) {
            event.preventDefault();
        }
    }

    for (i = 0, len = dropdowns.length; i < len; ++i) {
        element = dropdowns[i];
        element.dataNoclick = false;
        element.addEventListener("touchstart", menuTouch, false);
        element.addEventListener("click", menuClick, false);
    }
}

OK, it’s a bit of a hack, but it means your website can become usable again for touch devices; hack job is done. Better would be a navigation system that doesn’t rely on hover events, which touch devices can’t give you, but that’s for your website’s redesign…