Skip to content

Implementing Interactivity

Weston Ruter edited this page Jun 21, 2018 · 12 revisions

As noted in Adding Theme Support, the baseline experience after enabling AMP on an existing theme is that it largely behaves as if JavaScript is disabled in the user's browser. Many themes and plugins already have fallbacks in case JavaScript is not available so that will serve them well in AMP. Therefore, most of the work entailed is to progressively enhance a theme for AMP to restore features that were lost during this graceful no-JS degradation to AMP. These enhancements for AMP generally involve the use of modern CSS selectors (e.g. :focus-within), AMP components (e.g. amp-carousel and amp-position-observer), or AMP scripting via amp-bind. With these you can achieve full feature parity in AMP with the same interactivity you expect normally in a theme. This page documents some of the common solutions.

Hamburger Menu Toggle

Hamburger menu collapsed Hamburger menu expanded

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 ( is_amp_endpoint() ) conditionals.

Keyboard-Accessible Nav Menus

Nav sub-menu dropdowns

When a site has a nav menu with a hierarchy of sub-menus, it is normal for these to be displayed as a series of dropdowns which are made visible when hovering of focusing on a menu item. If you want to access a tertiary sub-menu you can hover over the parent submenu item in the secondary sub-menu: the :hover applies to the entire element tree. However, if you are using the keyboard then :focus will not behave the same way. For this reason themes often use jQuery to toggle a focus class on each ancestor for whatever item is currently focused. This doesn't work in AMP since custom JavaScript is not allowed. Nevertheless, there is now a way to achieve the same effect just with CSS via the new :focus-within pseudo selector. Beware that this is not yet recognized in IE/Edge (Can I Use?), so if you are on a paired AMP site and want to have a JS solution in addition to an AMP one, you'll need to copy the ruleset specifically for using :focus-within. In the following example, the focus class continues to be used in non-AMP:

.main-navigation li:hover > ul,
.main-navigation li.focus > ul {
	left: auto;
	right: 0;
}

/* The :focus-within selector is not recognized by IE/Edge. */
.main-navigation li:focus-within > ul {
	left: auto;
	right: 0;
}

.main-navigation ul ul li:hover > ul,
.main-navigation ul ul li.focus > ul {
	left: auto;
	right: 100%;
}

/* The :focus-within selector is not recognized by IE/Edge. */
.main-navigation ul ul li:focus-within > ul {
	left: auto;
	right: 100%;
}

Resources

  • See pull request for adding AMP support for core themes.
  • See pull request for adding AMP support to the underscores starter theme.
  • See WP Rig which has built-in support for AMP.

Clone this wiki locally