Since WordPress introduced native support for navigation menus in version 3.0, we can find them on almost every website. Undoubtedly they are popular, intuitive, and easy to use for most of your website visitors. But at the same time, a mouse-hover dropdown navigation menu anticipates that your visitors will sit in front of the screen and use a specific input device to interact with your site: a mouse. Unplug the mouse (or disable your trackpad if that’s how you control it), and the second most vital part of every website (after the actual content) vanishes. We can do better than that! This article will explain a small but important step in making your WordPress site a better place for all people with minimal effort.
We will take a look at how to expand the behavior and make dropdown navigation in your WordPress site richer in terms of usability and independent of the specific input devices your visitors tend to use.
The term browser in this article is used more broadly than you might have realized so far. In addition to the major desktop browsers you know already, think about screen readers or any other device capable of reading an HTML document.
What is WAI-ARIA?
WAI-ARIA is an abbreviation for the Web Accessibility Initiative – Accessible Rich Internet Applications, and if you follow a few simple rules you can develop a site that is much more user-friendly for people who use different devices and software to browse the web. Take this example: around 7% of adults are blind or have serious difficulty seeing. Many developers are still forgetting about them despite the fact there are more (semi-)blind people, and it is easier and more important to provide a solution for them than forcing an HTML5 site to function flawlessly. This is why you should seriously consider using WAI-ARIA specs.
Let’s make things clear. WAI-ARIA attributes will not change the layout or style of your pages but they will make your site richer with information for screen readers and other similar assistive technologies.
There are two parts of the WAI-ARIA specification that we will take a closer look at: landmark roles and ARIA states.
Landmark roles
ARIA landmark roles are attributes in HTML that can drastically improve the accessibility of your website. Their main purpose is to give screen readers an option for fast access to the main areas of a page. Currently there are 8 roles available:
role="banner"
– Used for the header of the website. Inside we will usually find the logo, search, navigation, etc. It is recommended to use only one banner role per page.role="navigation"
– Used for links and menus for navigation through the site. We will use this one later.role="main"
– This is for our main content area.role="complementary"
– This role is used in a sidebar. You can use it more than once.role="contentinfo"
– Used for the footer area. Just likebanner
role,contentinfo
should be used just once on a page.role="search"
– WordPress uses this role by default but if you make a custom search form you should add this role.role="form"
– This should be used if any kind of form is present on the page.role="application"
– If a page has some kind of unique software, use this role.
ARIA states
ARIA states are specific pieces of information that usually change after some kind of action has been taken by the user. We will see how all of this works in the dropdown menu example below.
Learn more about global states and properties.
Making your WP navigation menu accessible
Time to get your hands dirty. Let’s take a look at how to implement the WAI-ARIA landmark roles and states properly for the dropdown menus in WordPress.
Menus are inserted into theme templates using the wp_nav_menu()
function. For compatibility it is always best to rely on the native functions available in the WordPress core. Fortunately, wp_nav_menu()
is easily configurable, so we can provide the right parameters to the function to make our dropdown navigation friendly for assistive technologies and keyboard navigation.
PHP, HTML and WordPress
Let’s take a look at this code example from the header.php
file from one of our themes:
<nav class="collapse navbar-collapse" role="navigation" aria-label="<?php _e( 'Main Menu', 'textdomain' ); ?>">
<?php
if ( has_nav_menu( 'main-menu' ) ) {
wp_nav_menu( array(
'theme_location' => 'main-menu',
'container' => false,
'menu_class' => 'main-navigation',
'walker' => new Aria_Walker_Nav_Menu(),
'items_wrap' => '<ul id="%1$s" class="%2$s" role="menubar">%3$s</ul>',
) );
}
?>
</nav>
First, we have a <nav>
wrapper around the function call. <nav>
is an HTML5 element for grouping navigation links together, but it needs some more context, namely these two additional attributes:
role="navigation"
is the landmark role telling the browser there will be a menu inside, so the screen reader can jump straight to this section of the page if the user decides to.aria-label="<?php _e( 'Main Menu', 'textdomain' ); ?>"
tells the browser the title of the current menu. Other examples of labels are Sidebar menu, Footer menu or even Table of contents. I used the_e()
i18n function here to output the actual text, so the menu label can be translated into languages other than English — another important aspect for accessibility.
What about the configuration parameters we pass to wp_nav_menu()
?
'container' => false
prevents our menu wrapping to an additional<div>
.'walker' => new Aria_Walker_Nav_Menu()
is the most crucial parameter. It tells the menu to use our custom menu walker and not the default one when iterating through menu items. This is important, as theAria_Walker_Nav_Menu()
walker outputs slightly modified HTML markup, with WAI-ARIA attributes properly implemented. Magic!'items_wrap' => '<ul id="%1$s" class="%2$s" role="menubar">%3$s</ul>'
is the outermost<ul>
container of our menu. By default this parameter lacks the attributerole="menubar"
, which represent the horizontal menu, hence we fix that with custom code (don’t mind strange strings like%1$s
, they are replaced dynamically upon PHP execution).
Of course, when wp_nav_menu()
is called, the class Aria_Walker_Nav_Menu
should exist already in the global space. Download this file and place it somewhere in your theme folder and then include it in your functions.php
:
require_once "path/to/aria-walker-nav-menu.php";
This walker aims to resemble the default WP nav walker as much as possible, the only difference being the accessibility improvements; role="menu"
is added to the output of the method start_lvl
and several other attributes are added to start_el
: role="menuitem"
for all <li>
elements and aria-haspopup="true" aria-expanded="false" tabindex="0"
for <li>
s with submenus.
Explanation? aria-haspopup
indicates whether <li>
controls the popup-like behavior of its descendants — which a dropdown menu obviously is. aria-expanded
describes the state of the “popup” and defaults to false
(invisible/closed dropdown menu). Lastly tabindex="0"
allows the element to reach the focus when the site is navigated with a keyboard — by default only links and form elements can reach the focus.
JavaScript
Because accessibility should be a fundamental part of every website, we want to avoid using JS as much as possible. It would actually be easier to add the attributes mentioned above via JS, but that would also be more error prone than having them in the source HTML — JS can break, HTML and CSS can’t.
But, nevertheless, we cannot avoid using JS when we need to update the ARIA states. That’s why we need to enqueue the following piece of JavaScript code:
jQuery( function ( $ ) {
// Focus styles for menus when using keyboard navigation
// Properly update the ARIA states on focus (keyboard) and mouse over events
$( '[role="menubar"]' ).on( 'focus.aria mouseenter.aria', '[aria-haspopup="true"]', function ( ev ) {
$( ev.currentTarget ).attr( 'aria-expanded', true );
} );
// Properly update the ARIA states on blur (keyboard) and mouse out events
$( '[role="menubar"]' ).on( 'blur.aria mouseleave.aria', '[aria-haspopup="true"]', function ( ev ) {
$( ev.currentTarget ).attr( 'aria-expanded', false );
} );
} );
All this does is that once any element with the aria-haspopup="true"
attribute triggers focus or blur events, the corresponding aria-expanded
state is updated to true
or false
, respectively.
Enqueue this file with the following PHP code inside functions.php:
add_action( 'wp_enqueue_scripts', function() {
wp_enqueue_script( 'textdomain-wai-aria', get_template_directory_uri() . '/path/to/wai-aria.js', array( 'jquery' ), null );
} );
And you’re done! Your site navigation makes much more sense now to screen readers and other similar devices. But we are not totally finished yet.
CSS
The last thing we have to do is to make the navigation menus keyboard-accessible. When someone is using the TAB
key, they should be able to navigate into submenus. This is not the case by default, but we can easily fix this.
For the mouse-hover dropdown menus, you probably already have something like this in your SCSS:
ul.main-menu {
li.menu-item-has-children {
ul.dropdown-menu {
position: absolute;
left: -9999px;
...
}
}
li.menu-item-has-children:hover {
ul.dropdown-menu {
left: 0;
top: 100%;
...
}
}
}
All you have to do is to add the [aria-expanded="true"]
CSS selector next to all selectors that have the :hover
state defined already. An altered example from above would then look like this:
ul.main-menu {
li.menu-item-has-children {
ul.dropdown-menu {
position: absolute;
left: -9999px;
...
}
}
li.menu-item-has-children:hover,
li.menu-item-has-children[aria-expanded="true"] {
ul.dropdown-menu {
left: 0;
top: 100%;
...
}
}
}
Conclusion
Mastering WAI-ARIA is not very easy, and it took us several days plowing through the official specification, recommendations and use cases scattered all over the web. It is hard to find the right information, but once you know what to use, the implementation itself is not hard at all.
That’s why we decided to create a generic solution and make it open source. The code above is available as a Composer package on GitHub and licensed under GPLv2, so everyone can include it easily in any WP project, free or paid.
Let me finish this article with a quote that changed my view about accessibility from A List Apart:
We need to change the way we talk about accessibility. Most people are taught that “web accessibility means that people with disabilities can use the Web”—the official definition from the W3C. This is wrong. Web accessibility means that people can use the web.