Jason G. Designs

WordPress Themes, Design Tutorials, Linux Themes and more…

Hiding and Showing a WordPress Menu on Mobile Devices

Dropdown menu exampleRecently, I added a menu feature to the theme for this website as well as for Urban Square awhile back. The feature is a button that hides and shows a menu specifically designed for a mobile phone. The same menu would be used for the desktop layout, but styled differently for the mobile phone. I used CSS to hide the button above certain screen widths and jQuery to hide and show the menu when the button is clicked (within the smaller screen width). In this tutorial, I will teach you, my readers, how I did it.

The target audience for this tutorial is designers and developers who are creating a theme from scratch, as many responsive themes, such as TwentyFourteen, already have this idea covered.

What you will need to get started

I would highly advise those following this tutorial to work in a local installation of WordPress. See Installing WordPress Locally. Working on a local computer ensures that live changes aren’t shown to actual readers on your site.

Ideally, your scratch theme’s development would be far along enough to have made your theme’s main layout responsive, but not have created your menus yet. That may not be necessary though, as the menu code can be adjusted slightly. Also, of mention is that you might be using different media query breakpoints for your responsive themes, so adjust things to fit for your theme design if desired.

Let’s get started

  • Setup our menu (or menus)
    You may have already implemented a menu or menus into your theme. The most important part here is to add a container class to your menu. If you want to add a second menu you can assign a different class name so that the hideshow script that we will create later can be applied to either one menu or more than one at a time. We will also account optionally for a fallback menu that uses wp_list_pages.

    To create our menu, we will follow the Codex instructions. Place the following in your theme’s functions.php file. If you already created menus, you can skip this part.

    function register_my_menu() {
        register_nav_menu( 'header-menu', __( 'Header Menu', 'theme-text-domain' ));
    }

    …or if you want more than one menu

    function register_my_menus() {
        register_nav_menus(
            array(
                'header-menu' => __( 'Header Menu', 'theme-text-domain' ),
                'sidebar-menu' => __( 'Sidebar Menu', 'theme-text-domain' )
            )
        )
    }

    The titles and slugs representing Header and Sidebar menus are suggestions. They can be titled anything that makes sense for the theme you’re creating. It is a good idea to title them according to their intended locations. theme-text-domain gets replaced with your theme’s text domain name that is used for localization. If you are not localizing your theme, replace the title with just 'Header', for example.

    The next step in menu creation is to add the menu(s) to their proper location in the theme files. For the purposes of this tutorial, we will work with one menu. Where this is placed in your theme depends on where you want it to show up in the layout order. In the design for this website, I placed the menu just outside of the closing header tag.

    ...
    </header>
    <?php wp_nav_menu( array(
        'theme_location' => 'header-menu',
        'container' => 'nav',
        'container_id' => 'menu',
        'container_class' => 'main-menu-container',
        'depth' => 1,
        'fallback_cb' => 'default_menu'
    ) ); ?>
    ...

    One important thing to note here is that we assigned a container_class of main-menu-container. That class will be used later by our jQuery script to target this specific menu. A few other things to mention is that in my theme the depth is set to 1, but you can make the depth any number. To keep this tutorial simple, I will just hide the first menu level, showing later how to hide and show submenus, if desired. Also, we have a fallback menu of default_menu. From WordPress 3.5 and above, no HTML will be shown if there are no menu items. So optionally, if you (or your theme’s end users) don’t want to use a custom menu, in comes a fallback menu. For a fallback menu, add the following to functions.php:

    function default_menu() { ?>
    <nav id="menu" class="main-pagemenu-container">
    	<ul>
    	<?php wp_list_pages('title_li=&depth=1'); ?>
    	</ul>
    </nav>
    <?php 
    }

    We give it no title as that will be added to our button. The depth should match the depth set in our wp_nav_menu code.

  • Add our buttonThere are two ways we can add the menu button. One way is to add it to the page for all screen sizes first, then hide it via CSS for all users over a certain width breakpoint, or hide it from the desktop completely by loading the code only if users are on a mobile phone. I went with the first option for my themes, but the second option is more foolproof. In the (unlikely) scenario where you are in a screen size smaller than, say, 640 pixels on a desktop computer, the button for mobile devices only will show using CSS hiding techniques.In header.php above the nav menu code, add:
    <?php if ( wp_is_mobile() ) { ?>
    <div class="button-panel">
        <button class="menu-button-title" title="<?php esc_attr_e( 'Menu:', 'theme-text-domain' ); ?>">
            <span class="menu-label"><?php _e( 'Menu:', 'theme-text-domain' ); ?><span class="icon-bars"></span><span class="icon-sort-desc"></span>
        </button>
    </div>
    <?php } ?>
    <?php wp_nav_menu( array( ...

    The added menu-button-title class is important; it will be referenced by our script later. The actual title “Menu:” is there optionally, for larger mobile screens like tablets. In your design, you may or may not want to hide and show menus on tablets, it depends on what you would like. In our mobile design, we will hide this title. The classes “icon-bars” and “icon-sort-desc” are for the embedded Fontawesome icons I will supply shortly.

    It is a good time now to setup folders for the script and fonts. Once again, if you already have folders for scripts and custom fonts, you can adjust the names accordingly. I added a fonts folder and a scripts folder to my theme.

    Now you can download the small subset of Font Awesome font icons I used for Urban Square. Follow the link to the Media folder to grab the icon font. The license for this font icon set allows you to redistribute freely. If you want the whole set or other icon sets, I recommend IcoMoon, which is where I got this set from. Unzip the file using your operating system’s unzip or file extraction utility, and go into the fonts folder. Copy the four files inside and Paste into your theme’s fonts folder.

    Now we need to embed the fonts. Add this to your theme’s style.css file:

    @font-face {
        font-family: 'fontawesome';
        src:url('fonts/fontawesome/fontawesome.eot?');
        src:url('fonts/fontawesome.eot?#iefix') format('embedded-opentype'),
            url('fonts/fontawesome.woff?') format('woff'),
            url('fonts/fontawesome.ttf?') format('truetype'),
            url('fonts/fontawesome.svg?#fontawesome') format('svg');
        font-weight: normal;
        font-style: normal;
    }
    
    [class^="icon-"], [class*=" icon-"] {
        font-family: 'fontawesome';
        speak: none;
        font-style: normal;
        font-weight: normal;
        font-variant: normal;
        text-transform: none;
        line-height: 1;
    }
    
    .icon-bars:before {
        content: "\f0c9";
    }
    
    .icon-sort-desc:before {
        content: "\f0dd";
    }

    If your theme already has a folder for embedded fonts, adjust the paths above to match your theme’s. The style rules in brackets target any class that has the name .icon-, the one’s used by Font Awesome. The :before psuedoclass is what adds the icons to any element that has the classes .icon-bars and .icon-sort-desc, the icons referenced in our <span> tags in our button.

  • Style our buttonI included very little styling for the button in this tutorial, as the look will be determined mostly by your theme. To make it “finger friendly” I gave it a width and height of 72px. You probably will want to target a mobile screen size via a media query in your stylesheet.
    @media screen and (max-width: 40em) {
        .menu-button-title {
            width: 72px;
            height: 72px;
        }
    
        .menu-label {
            position: absolute;
            left: -9999px;	
        }
    }

    This is where things can get tricky, as everyone sets up CSS media queries differently. Some may use em measurements or pixels, some people might set breakpoints according to popular device sizes, known screen sizes or simply what looks good to the eye of the designer. The point is to adjust to whatever media query width that makes sense for a small screen in your design.

    Also of note is that in a mobile first approach, a minimum width is used, usually eliminating the need for a media query for mobile phones. In such a case, the .menu-button-title style rule can be placed outside of a media query. Then in the next size up, perhaps tablet size, you can hide the button:

    @media screen and (min-width: 40.063em) {
        .menu-button-title {
            position: absolute;
            left: -9999px;
        }
    }

    So far as styling the menu itself for the different screen sizes, that is best left up to the designer. Ideas on how to style the menu would make this tutorial much longer. But these pointers may be of use. If using a traditional list menu, display: block for list items in the mobile media query if they are displayed as inline in your desktop media query. Likewise, if you are using floats to float: left your list items, float: none and clear: both in mobile.

  • Create and Enqueue Hideshow jQuery scriptNow we can create the hideshow script. Create a new file in your text editor and copy/paste the following:
    /* HideShow Mobile */
    
    /* By Jason G. Designs */
    /* see http://www.jasong-designs.com/2015/11/21/hiding-and-showing-a-wordpress-menu-on-mobile-devices/ */
    /* The license for this script is GNU General Public License */
    /* https://www.gnu.org/licenses/gpl.html */
    
    jQuery(document).ready( function( $ ) { // opens document ready
    
    }); // closes document ready

    Save this file in your scripts directory as hideshow-mobile.js. Then, enqueue the script into your WordPress theme in your functions.php file:

    function enqueue_hideshow_script() {
        wp_register_script( 'hideshow-mobile', get_template_directory_uri() . '/scripts/hideshow-mobile.js', array( 'jquery' ), '1.0' );
        wp_enqueue_script( 'hideshow-mobile' );
    }
    
    add_action( 'wp_enqueue_scripts', 'enqueue_hideshow_script' );

    This can be conditionally enqueued for mobile phones only using wp_is_mobile(), but enqueueing it globally works better if you want to apply some hideshow action to your desktop design as well.

    Now we can add variables that will hold our menu class references and window width:

    jQuery(document).ready( function( $ ) { // opens document ready
    
    /* Hide/Show top navigation menu in mobile screen size */
        // define variables to hold data
        var windowWidth;
        var menuTitle = ".menu-button-title";
        var customMenu = "nav.main-menu-container";
        var pageMenu = "nav.main-pagemenu-container";
        var customMenuLink = ".menu .menu-item a";
        var pageMenuLink = "nav#menu ul .page_item a"
    
    }); // closes document ready

    nav.main-menu-container pertains to a custom menu, while nav.main-pagemenu-container pertains to the fallback menu.

    Next, we setup the initial browser state on page load. We will hide the menu when the page is first loaded.

        var pageMenuLink = "nav#menu ul .page_item a"
    
        // Setup our initial state in the browser on first load
        windowWidth = $( window ).width();
    
        if ( windowWidth <= 640 ) { // hide menu above mobile screen size
            $( customMenu ).hide();
            $( pageMenu ).hide();
    
        } // closes if statement

    Then we add our toggle functions, to open and close our menu at the click of a button:

            $( pageMenu ).hide();
    
            // Toggle menu
            $( menuTitle ).click( function() {
                $( customMenu ).slideToggle( "slow" );
                $( pageMenu ).slideToggle( "slow" );
            });
    
        } // closes if statement

    We can also add a function that closes the menu when the last link in the list is tabbed over. If you save the script file right now and click the menu button to close or open the menu, you can tab through each link. At the last link the menu stays open.

    Just before the closing if statement curly brace, add:

    // Hide menu after last link loses focus
            $( customMenuLink ).last().blur( function() { 
                $( customMenu ).slideUp( "slow" );
            });
    
            $( pageMenuLink ).last().blur( function() {
                $( pageMenu ).slideUp( "slow" );
            });
        } // closes if statement
  • So what if we have submenus?Well things get slightly more complicated, but doable. The code that is generated for a custom menu with sub menus looks similar to the code below:
    <nav id="menu" class="main-menu-container">
        <ul id="menu-custom-menu-2" class="menu">
            <li id="menu-item-323" class="menu-item menu-item-type-custom menu-item-object-custom menu-item-home menu-item-323"><a href="http://localhost/wordpress/">Home</a></li>
            <li id="menu-item-326" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-has-children menu-item-326"><a href="http://localhost/wordpress/contact-us/">Contact Us</a>
                <ul class="sub-menu">
                    <li id="menu-item-327" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-327"><a href="http://localhost/wordpress/contact-us/our-location/">Our Location</a></li>
                </ul>
            </li>
            ...
            <li id="menu-item-337" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-337"><a href="http://localhost/wordpress/support/">Support</a></li>
        </ul>
    </nav>

    The important classes here are .sub-menu, referring to unordered list just below a parent link. .menu-item-has-children denotes which pages have child pages. We will append a button with the appropriate class next to this with jQuery, in order to hide and show the submenus. For the fallback menu, those classes are .children and .page_item_has_children.

    We also need to reference .icon-sort-desc, our down pointing arrow font icon. Add these to our variable list in hideshow-mobile.js:

        // define variables to hold data
        var windowWidth;
        var menuTitle = ".menu-button-title";
        var customMenu = "nav.main-menu-container";
        var pageMenu = "nav.main-pagemenu-container";
        var customMenuLink = ".menu .menu-item a";
        var pageMenuLink = "nav#menu ul .page_item a";
        var menuChildren = "nav.main-pagemenu-container .children";
        var customMenuChildren = "nav.main-menu-container .sub-menu";
        var hasChildrenLink = ".page_item_has_children > a";
        var customHasChildrenLink = ".menu-item-has-children > a";
        var menuDownArrow = ".menu .icon-sort-desc";
        var menuChildrenLink = ".nav.main-pagemenu-container .children .page_item a";
        var customMenuChildrenLink = "nav.main-menu-container .sub-menu .menu-item a";

    Whether or not you want dropdown arrow buttons to appear in the desktop width or mobile only width will determine where you put the “append” code:

        $( hasChildrenLink ).after("<button class=\"icon-sort-desc\" title=\"Click or Tab and Press Enter to Open/Close Submenu\"></button>"); // add arrow icon
        $( customHasChildrenLink ).after("<button class=\"icon-sort-desc\" title=\"Click or Tab and Press Enter to Open/Close Submenu\"></button>");

    Refresh your browser at this point to see if the buttons show up next to the links.

    Place inside of the if ( windowWidth <= 640 ) { ... statement for these to show up in mobile size only. Place the above code outside of the if statement for the buttons to show up in desktop and mobile sizes.

    Then add the toggle code:

    // Toggle on click for menu down arrow icon
        $( menuDownArrow ).click( function() { 
            $(this).next().slideToggle( "slow" ); // this = .icon-sort-desc; next = .children/.sub-menu
        });

    With the way our auto generated code is set up, the ul tag with the class .sub-menu or .children should come right after this, which references our newly added button.

    Remember the code that mentions the last link closing the menu when tabbed over? With sub menus, replace that with the following:

        $( menuChildrenLink ).last().blur( function() { 
            $( pageMenu ).slideUp( "slow" );
        });
    
        $( customMenuChildrenLink ).last().blur( function() { 
            $( customMenu ).slideUp( "slow" );
        });

Finished!

As you can see, things can get complicated, especially if your menu has sub menus. But this is an essential feature for saving limited screen space on a mobile device. I hope you found this tutorial useful for your next project. Keep theming, y’all.

Update: There is now a follow up tutorial that describes how to create this same menu with pure JavaScript! Check it out.

1 Comment

  1. Anam Ahmed says:

    One of the best guide so far, I wanted to hide my top menu in mobile device, and your guide helped that to work!

Trackbacks and Pingbacks

Trackback URL for this post: https://www.jasong-designs.com/2015/11/21/hiding-and-showing-a-wordpress-menu-on-mobile-devices/trackback/

  1. […] so this is a continuation of a previously written tutorial, Hiding and Showing a WordPress Menu on Mobile Devices. In that tutorial, I described how to create a JS controlled dropdown menu that turns into a button […]

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.