iPad dropdown menus

Implement Responsive Drop Down Menus that Work on iOS and Android

Drop down menus on iOS and Android (or any devices with touch screens) can be really problematic if the top-level menu item is also a link. The problem is due to the fact that touch screen devices have no hover, so the user has to touch the top-level menu item to see the sub-menu items, but this can trigger the top link, making the sub-menu items impossible to get to!  I’ve seen this in many cases in websites in the wild! 

Complicating things is a bizarre bug on iOS, whereby if a YouTube video is on the same page below your menu, it causes the top-level menu item to navigate when touched, instead of showing the dropdown!

The Problems with CSS Drop Down Menus on Touch Screens

I encountered problems with CSS drop-down menus on iOS and Android when working with a popular WordPress starter theme called Underscores.  This was my “go to” starter theme, and I had used it many times.  It was fine until my client asked me to make the top-level menu items links.  “No problem”, I said.  When I did it, and tested the menu on iOS, the top-level menu items double-clicked and navigated to the links instead of pausing to show the dropdown, and it was impossible to access the sub-menu items below them.  This same functionality had worked in previous websites I had worked on.  What gives??

After hours of investigation, I discovered that the Underscores theme had been modified to use “left: -9999em” to hide the drop down menu instead of “display: none”.  To reveal it, it used “left: auto” instead of “display: block”.

To understand why touching a top-level link with this type of sub-menu causes a “double click”, you have to understand how the iPad deals with hover events.  When you touch a link on an iPad, it first triggers the hover event.  It then checks for any “display:” or “visibility:” changes (due to CSS or JavaScript).  If there are no changes in “display:” or “visibility:”, it triggers the click event.  If there are changes, then it stops and just displays the changes, which in this case is a drop down menu, allowing the user to choose a sub-menu item.

The problem with the new Underscores theme was that “display” was replaced with “left”.  iOS doesn’t stop when a “left” changes value, so it went ahead and clicked on the link.

So, reverting the CSS dropdown to use “display” fixed this problem.  I thought I was done, but I was a long way from it.

For one thing, if you use pure CSS hover to do your dropdown menus, there’s no way to close the dropdown menu on touch screens.  iOS will trigger the hover event when you tap on a parent menu item once, but there’s no way to “un-hover” the submenu, so it will stay open until you open another submenu or navigate to a different page. This can be fixed, if necessary, by using JavaScript instead of CSS.

Second, using “display:none” will make the content invisible to screen readers and and violate Section 508 Compliance. This is probably the reason that Underscores changed the CSS from “display:none” in the first place.
THE domain at THE price! $.99 .Com Domains from GoDaddy!

The Bizarre Problem with All CSS Drop Down Menus and YouTube Videos on iOS

The CSS fix I described fixed the parent link nav problem for most of the pages, except for those with YouTube videos in them. When the page had a YouTube video, and if you touched a menu item above the YouTube video, the page would navigate to the link instead of stopping to show the dropdown, making it impossible to get to the sub-menu items.

In the example below, the green YouTube video at the bottom is causing the circled top-level menu links to malfunction in iOS. Touching them causes you to navigate to the link instead of stopping to reveal the dropdown menu:

YouTube iOS menu problemIf I removed the YouTube video, the menu would work properly.  This was really baffling.  I Googled the problem and discovered that other people had experienced it as well.  There was no known solution. This was crazy!  Why would a YouTube video cause navigation problems in a far-away menu (more specifically, it only seems to affect menu items directly above it in the browser)??

I tried a variety of things, but none worked. It seemed that JavaScript was needed.

If you only need your app to work on iOS, and you don’t have any YouTube videos, you can stop here.  You can use the all-CSS solution.

The Solution to the YouTube / iOS Problem

For some reason, switching from CSS to JavaScript to handle the drop down menus fixes the problem. Here’s the standard HTML for a menu with dropdowns:

I’ll exclude most of the CSS, except for the one line to hide sub-menus.

Here’s the JavaScript to show and hide the sub-menu items:

I’m sure there’s a more elegant way to do it.  Please comment if you can improve!

With this, we have a workaround which solves the mysterious YouTube problem on iOS. Menus with dropdowns will now work on pages with YouTube videos on iPad, iPhone and iPod (I am still baffled at how ludicrous this bug is).  It seems the bug only affects CSS, and if you write JavaScript to do the exact same thing, you don’t get the YouTube problem, for unknown reasons.

If you don’t care about Android, you can stop here.

The Problem with CSS Drop Down Menus and Android

Most likely you need your website to work on Android as well.  I only have one Android device to test on: an HCT One.  That device doesn’t do Apple’s trick of triggering a hover on touch, and waiting to see if the “display” changes.  When I click on a top-level menu on Android, it just navigates to the link, making the sub-menu items impossible to access. (Is this the case on other Android devices? Please comment below.)

There was yet another problem on Android.  My site was responsive, so when the browser was narrow, the menu would turn into a vertical stack, with sub-menu items initially hidden but revealed upon pressing a main menu item:Stacked mobile menuThis sort of worked on Android except for strange behavior when I opened a sub-menu below an already-open sub-menu.  Often it would navigate to the wrong link instead of just opening the sub menu.

I figured out that the problem was a race condition between the triggering of the link and the closing of the previous sub-menu.  The previous sub-menu would start to close before the link triggered.  As the menu collapsed, the touch event would trigger on some random link that happened to be moving below where I touched it.  Not good. Again, JavaScript to the rescue.

Fixing Menus on Android

My solution below is kind of ugly for two reasons.  First, I take a big shortcut and simply disable top-level menu links on Android (and any other non iOS mobile devices).  On my application, the top-level menu items have the same link as the first sub-menu item, so the links are redundant and disabling them results in no loss of functionality. If you need your top-level menu items to work, you’ll have to write some more complicated JavaScript to keep track of the click state of the top menu item (whether the drop-down has been opened or not), then decide whether to navigate to the link or open the drop-down.

Second, it uses the much-dreaded browser sniffing.  Basically, we want to look for all touch device except for iOS.  If anyone has a better method, let me know!

The Full Solution for Android and iOS

Here’s the full JavaScript:

Basically, the additional JavaScript just disables any top-level menu links that have sub-menus.  In WordPress, a class of “menu-item-has-children” is automatically assigned to top-level menu items which have sub-menus. If you are not using WordPress, just add this class to any top-level menu items which have dropdowns.

Note, the fadeOut delay is very important when the menu is stacked and used on the stock Android browser. It prevents the race condition I mentioned.  A much smaller delay is required (10ms) if you only care about the Android Chrome browser.

Another important note: since the above solution uses display:none to hide the dropdowns, it’s not Section 508 compliant. However, you can easily fix that by using “left:-15000px” or something similar to move the dropdowns off-screen, as shown below.

A Solution for Mobile Sub-Menus You Can Open and Close

I re-visited this problem with a slightly different scenario this time. My parent menu items (which had sub-menus) were dead links, but on mobile, I wanted the ability to open and close the sub-menus by clicking the parent link. This is not possible with the previous method.

Also, in this case, I needed the menus to be Section 508 compliant, so I couldn’t use display:none to hide the dropdowns.

The way I solved it is to detect when I am in mobile mode (by checking if the hamburger icon is hidden or not), and triggering the submenus off of a click event instead of hover. This also has the side-effect of eliminating the need for browser detection, since the stacked menu triggers off of clicks now.  Here’s the code for the Underscores theme:

Note, if you are using the Underscores theme, you will have to remove the code in navigation.js which is commented, “Toggles focus class to allow submenu access on tablets.”  That code interferes with the code above.

Also, if you re-size the browser window and cross the mobile device width threshold, strange things might happen with this code. If you want to avoid that, you’ll have to trigger on browser width and change behavior accordingly.

Summary

I’ve gone through a lot of material here, so I’d like to summarize my findings and general rules of thumb:

  1. Use JavaScript rather than CSS to implement your drop-down menus.  If you use CSS, there no way (that I know of) to close the drop down menu on touch screen devices (no way to “un-hover” on a tablet). Furthermore, if you use CSS hover with absolute positioning to hide your dropdown as described below, your submenu won’t be accessible on touchscreen devices.
  2. If you use “display:none” to hide the dropdown, the dropdown menu items won’t show up on a screen reader and might be in violation of Section 508 compliance rules. Instead, move the dropdown off screen using JavaScript and CSS absolute positioning, then move it back when you want to show it.
  3. You can make the parent menu item of a dropdown have a real link, but it causes some issues on a mobile (stacked) menu. On a phone, I like to have that top menu item open and close the submenu.  If that item is a link, that won’t work and you’ll either have to have the submenus all permanently open on mobile (which is what some themes do), or come up with some other scheme to show and hide the submenus.

Well, that’s it.  These may not be perfect solutions, but we’re dealing with some complex problems here. Let me know if you have better solutions.  I would welcome it!! – Brian

23 thoughts on “Implement Responsive Drop Down Menus that Work on iOS and Android

  1. Hi,
    I was so happy to find this because the site I am working on does require the top level nav terns to be links. However when I made the change from left: -9999em; to display: none; for hiding (and related for showing) and set left: 0; I am finding that the site is so slow that the iPad (and phone too) that it totally freezes up. I can’t imagine why this small css change would impact the display on mobile so much. Any thoughts?

    I will try the javascript solution above for the YouTube issue incase that helps for this problem but I am not hopeful that adding more complexity will make things run faster.

    heather

  2. I also have an issue with a WordPress Site menu that has no sub-menus. On the desktop site, it appears fine, but has some :hover styling, etc. I am looking to just remove all :hover capability on my site so that I can have the mobile/tablet menu be single-click (no :hover for 1st click). Can anyone help with some CSS to deactivate :hover styling so that mobile menus will act with 1 click?

    1. Nick I am having same issue with drop down menus but cannot see how to make the top menu not have a link. I tried putting in a Title only menu item and wordpress wanted the title to be either linked to a page or an URL.

      Can you please describe in more detail what you did . Many thanks

      1. I don’t use WordPress Nigel so I’m not sure how to do it

        I use a third party javascript menu creator then copy and paste code

        Would ravensongdigital solution of May 25, 2015 at 7:13 PM work?

        Sorry not much help!

  3. For what it’s worth, a solution I have been using, on WordPress sites where we want the top level menu item to NOT be a link because I find that confusing for the user – are all of the options in the submenu, or is the top item also an option?

    Make the top level items links to “#” so that they will trigger the dropdown, but don’t go anywhere.
    Then use CSS to make them not APPEAR to be links. I use the wordpress-assigned class “.menu-item-has-children” and set text decoration to ‘none’, and cursor to ‘default’. So it’s a link that triggers the dropdown on hover(desktop)/press(ios) but does not otherwise look or act like a link.

    However, that will also cascade to the submenu, so link behavior must be reinstated there, using “ul.sub-menu li a” and set the cursor back to ‘pointer’.

    Seems to be working okay for my sites on both desk and mobile.

    1. I don’t know of a solution on the user side. Yeah, it’s worth trying different browsers. If you find one that solves it, please let me know.

      Thanks,
      Brian

  4. To fix child menus for touch devices. Use this updated javascript in navigation.js when you use underscores. Source of this code is: https://planedemo.wordpress.com/

    /**
    * navigation.js
    *
    * Handles toggling the navigation menu for small screens.
    */
    ( function() {
    var container, button, menu;

    container = document.getElementById( ‘site-navigation’ );
    if ( ! container ) {
    return;
    }

    button = container.getElementsByTagName( ‘button’ )[0];
    if ( ‘undefined’ === typeof button ) {
    return;
    }

    menu = container.getElementsByTagName( ‘ul’ )[0];

    // Hide menu toggle button if menu is empty and return early.
    if ( ‘undefined’ === typeof menu ) {
    button.style.display = ‘none’;
    return;
    }

    menu.setAttribute( ‘aria-expanded’, ‘false’ );

    if ( -1 === menu.className.indexOf( ‘nav-menu’ ) ) {
    menu.className += ‘ nav-menu’;
    }

    button.onclick = function() {
    if ( -1 !== container.className.indexOf( ‘toggled’ ) ) {
    container.className = container.className.replace( ‘ toggled’, ” );
    button.setAttribute( ‘aria-expanded’, ‘false’ );
    menu.setAttribute( ‘aria-expanded’, ‘false’ );
    } else {
    container.className += ‘ toggled’;
    button.setAttribute( ‘aria-expanded’, ‘true’ );
    menu.setAttribute( ‘aria-expanded’, ‘true’ );
    }
    };
    // Fix child menus for touch devices.
    function fixMenuTouchTaps( container ) {
    var touchStartFn,
    parentLink = container.querySelectorAll( ‘.menu-item-has-children > a, .page_item_has_children > a’ );

    if ( ‘ontouchstart’ in window ) {
    touchStartFn = function( e ) {
    var menuItem = this.parentNode;

    if ( ! menuItem.classList.contains( ‘focus’ ) ) {
    e.preventDefault();
    for( var i = 0; i < menuItem.parentNode.children.length; ++i ) {
    if ( menuItem === menuItem.parentNode.children[i] ) {
    continue;
    }
    menuItem.parentNode.children[i].classList.remove( 'focus' );
    }
    menuItem.classList.add( 'focus' );
    } else {
    menuItem.classList.remove( 'focus' );
    }
    };

    for ( var i = 0; i < parentLink.length; ++i ) {
    parentLink[i].addEventListener( 'touchstart', touchStartFn, false )
    }
    }
    }

    fixMenuTouchTaps( container );
    } )();

  5. Brian, I am having this problem with drop-down menus, in a wordpress site, where the parent item IS NOT a link. Touching the parent item does nothing in iOS, the drop-down does not appear. The site is northernlights300.org Any advice?

  6. Thank you for laying out things for us, Brian. I’m trying to implement a native-like slide out menu for a website, but it keeps acting freaky on Android, where a single touch sometimes equates to a double touch (thus opening and closing the menu). Works fine everywhere else though (Wattup with that, Google??) Anyway, I threw in a stop() to try clean up the motion and prevent interruption, but as a novice for Android web development, I’ll definitely have to pocket your article and read it later.

    1. Hi Ronson,

      Thanks for your comments! The Foundation 5 framework comes with a cool slide-out menu that works on Android. Here is where I used it:
      http://lalindyhop.com/

      But, of course, it’s a whole framework that you’d have to deal with (it uses SASS and Grunt)…

      Anyway, good luck with your project! I enjoyed checking out your site and blog!

      Thanks,
      Brian

  7. Hey Brian – This post is very helpful. The only problem is.. for me on iPad the hover style is triggered correctly, but never goes away! I would expect it to be removed if the rest of the page is clicked. I have found a couple of ways of doing this, but cannot then get the hover state re-triggered subsequently, i.e. the sub-menu can only be shown/hidden once. Would really appreciate any ideas you might have?

      1. Thanks for your reply Brian. I’ve tested your site on iPad again (have tried iOS Xcode and real iPad mini 2, latest iOS version), for example on the About page once the sub-menu is down it will not go away if I tap or scroll the rest of the page. The Home page seems to react slightly differently (touching the hero image makes the menu go away), and of course the menu goes away if you click a link. Quite strange if this is not how it works for you.

    1. I see what you are talking about now! The menu does go away if you touch the hero image on the home page, but not anywhere else on the screen, and not on other pages! Good catch.

      Perhaps you could detect a click click anywhere on the screen to close the dropdown once it’s open? Let me know if you come up with a solution.

      Thanks!!!
      Brian

Leave a Comment or Ask a Question