Toggling Hamburger Menus

    Collapsed:

    Expanded:

    As of 1.1, the implementation of a mobile nav menu can be done via a new AMP theme support flag called nav_menu_toggle. For example:

    add_theme_support(
    	'amp',
    	array(
    		'nav_menu_toggle'   => array(
    			'nav_container_id'           => 'site-navigation',
    			'nav_container_toggle_class' => 'toggled-on',
    			'menu_button_id'             => 'site-navigation-toggle',
    			'menu_button_toggle_class'   => 'toggled-on',
    		),
    	),
    );

    If no element ID is available for nav_container_id, then you may instead use nav_container_xpath. Similarly, if there is no element ID available for menu_button_id, then you may use menu_button_xpath.

    Also consider the use of amp-sidebar if you are building at theme from scratch.

    Read on to see how to implement support in your theme without using the nav_menu_toggle theme support flag.


    On many themes when the template is served in a narrow viewport (e.g. on mobile) the nav menu is hidden behind a “hamburger” button to reveal it. This usually involves enqueueing some jQuery-based JavaScript code that does:

    $( '#menu-toggle' ).on( 'click', function() {
        var button = $( this ), nav = $( '#site-header-menu' );
        button.toggleClass( 'toggled-on' );
        nav.toggleClass( 'toggled-on' );
        button.attr( 'aria-expanded', button.hasClass( 'toggled-on' ) ? 'true' : 'false' );
        nav.attr( 'aria-expanded', button.hasClass( 'toggled-on' ) ? 'true' : 'false' );
    } );

    Since this custom JS is not available in AMP, an alternative is needed. The AMP alternative will have the benefit of not requiring any blocking JavaScript, such as the preceding example required (2 external scripts). The solution here is to use amp-bind. The amp-bind component lets you write a subset of JavaScript in which you define some state on the page (amp-state), manipulate the state (AMP.setState()), and then react to changes to the state (via the bracketed binding attributes).

    Here’s an example of defining a navMenuExpanded state property with a default value of false. Then there is a button which has an AMP on attribute that takes calls AMP.setState() to toggle the navMenuExpanded state when the user does a tap on it. Then both the button and the nav have their class and aria-expanded attributes bound to changes to the navMenuExpanded state so they get updated when it changes:

    <!-- 1. Define the state -->
    <amp-state id="navMenuExpanded">
            <script type="application/json">false</script>
    </amp-state>
    
    <!-- 2. Mutate the state -->
    <button
            class="menu-toggle"
            on="tap:AMP.setState( { navMenuExpanded: ! navMenuExpanded } )"
            [class]="'menu-toggle' + ( navMenuExpanded ? ' toggled-on' : '' )"
            aria-expanded="false"
            [aria-expanded]="navMenuExpanded ? 'true' : 'false'"
    >
            <?php _e( 'Menu', 'example' ); ?>
    </button>
    
    <!-- 3. React to state changes -->
    <nav
            class="site-header-menu"
            [class]="'site-header-menu' + ( navMenuExpanded ? ' toggled-on' : '' )"
            aria-expanded="false"
            [aria-expanded]="navMenuExpanded ? 'true' : 'false'"
    >
            <?php wp_nav_menu( /* ... */ ); ?>
    </nav>
    

    Naturally if you intend to serve this markup in non-AMP responses as well, you would want to wrap the AMP-specific elements and attributes in if ( example_is_amp() ) conditionals.