Skip to content

Instantly share code, notes, and snippets.

@jmccall75
Forked from SteveJonesDev/accessible-menu.php
Created February 14, 2023 13:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jmccall75/2718f3aef6500c756f6a405aa37ad911 to your computer and use it in GitHub Desktop.
Save jmccall75/2718f3aef6500c756f6a405aa37ad911 to your computer and use it in GitHub Desktop.
Accessible WordPress Navigation Menu
<div class="menu-container">
<button class="menu-button" aria-controls="site-header-menu"><span class="screen-reader-text"><?php esc_html_e('Menu','rwc'); ?></span></button>
<div id="site-header-menu" class="site-header-menu">
<nav id="site-navigation" class="main-navigation" role="navigation" aria-label="<?php esc_attr_e('Primary Menu', 'rwc'); ?>">
<?php
wp_nav_menu(array(
'theme_location' => 'primary',
'depth' => 3,
'menu_id' => 'primary-menu',
'container_class' => 'nav-primary'
));
?>
</nav>
</div>
</div>
/**
* An accessible menu for WordPress
*/
(function($) {
var menuContainer = $('.menu-container');
var menuToggle = menuContainer.find( '.menu-button' );
var siteHeaderMenu = menuContainer.find( '#site-header-menu' );
var siteNavigation = menuContainer.find( '#site-navigation' );
// Toggles the menu button.
(function() {
if (!menuToggle.length) {
return;
}
// Add aria-expanded attribute to the menu.
menuToggle.add(siteNavigation).attr('aria-expanded', 'false');
// Toggle the menu button.
menuToggle.on('click', function() {
// Add toggled-on class.
$(this).add(siteHeaderMenu).toggleClass('toggled-on');
// Add aria-expanded attribute value to the menu.
$(this).add( siteNavigation )
.attr('aria-expanded', $( this )
.add( siteNavigation ).attr( 'aria-expanded' ) === 'false' ? 'true' : 'false' );
} );
} )();
// Add the dropdown toggle button to menu items that have children menu items.
$('.menu-item-has-children > a').not(this).each(function(){
// Get link text to prepend to screen reader text.
var linkText = $(this).text();
// Define screen reader text.
var screenReaderText = {"expand":": submenu","collapse":": submenu"};
// Set submenu button with screen reader text
var dropdownToggle = $('<button />', {'class': 'dropdown-toggle','aria-expanded': false})
.append($('<span />', {'class': 'screen-readers',text: linkText+screenReaderText.expand}));
// Add submenu button after link
$(this).after(dropdownToggle);
});
// Adds aria attribute to the site menu.
siteHeaderMenu.find( '.menu-item-has-children' ).attr( 'aria-haspopup', 'true' );
// Toggles the submenu when dropdown toggle button is clicked.
siteHeaderMenu.find( '.dropdown-toggle' ).click( function(e) {
// close open submenus and reset attributes.
$('.dropdown-toggle').not(this).each(function(){
$(this).removeClass( 'toggled-on' );
$(this).nextAll( '.sub-menu' ).removeClass( 'toggled-on' );
$(this).attr( 'aria-expanded', $(this).hasClass( 'toggled-on' ) === 'false' ? 'true' : 'false' );
});
// open current submenu and set attributes.
e.preventDefault();
$(this).toggleClass( 'toggled-on' );
$(this).nextAll( '.sub-menu' ).toggleClass( 'toggled-on' );
$(this).attr( 'aria-expanded', $(this).attr( 'aria-expanded' ) === 'false'
? 'true' : 'false' );
});
// Adds a class to sub-menus for styling.
$('.sub-menu .menu-item-has-children').parent('.sub-menu').addClass('has-sub-menu');
// Keyboard navigation.
$('.menu-item a, button.dropdown-toggle').on('keydown', function(e) {
if ([37,38,39,40,27].indexOf(e.keyCode) == -1) {
return;
}
switch(e.keyCode) {
case 27: // escape key
$(this).parents('ul').first().prev('.dropdown-toggle.toggled-on').focus();
$(this).parents('ul').first().prev('.dropdown-toggle.toggled-on').click();
break;
case 37: // left key
e.preventDefault();
e.stopPropagation();
if ($(this).hasClass('dropdown-toggle')){
$(this).prev('a').focus();
}
else {
if ($(this).parent().prev().children('button.dropdown-toggle').length) {
$(this).parent().prev().children('button.dropdown-toggle').focus();
}
else {
$(this).parent().prev().children('a').focus();
}
}
if ($(this).is('ul ul ul.sub-menu.toggled-on li:first-child a')) {
$(this).parents('ul.sub-menu.toggled-on li').children('button.dropdown-toggle').focus();
}
break;
case 39: // right key
e.preventDefault();
e.stopPropagation();
if($(this).next('button.dropdown-toggle').length) {
$(this).next('button.dropdown-toggle').focus();
}
else {
$(this).parent().next().children('a').focus();
}
if ($(this).is('ul.sub-menu .dropdown-toggle.toggled-on')){
$(this).parent().find('ul.sub-menu li:first-child a').focus();
}
break;
case 40: // down key
e.preventDefault();
e.stopPropagation();
if($(this).next().length){
$(this).next().find('li:first-child a').first().focus();
} else {
$(this).parent().next().children('a').focus();
}
if (($(this).is('ul.sub-menu a')) && ($(this).next('button.dropdown-toggle').length)) {
$(this).parent().next().children('a').focus();
}
if (($(this).is('ul.sub-menu .dropdown-toggle')) && ($(this).parent().next().children('.dropdown-toggle').length)) {
$(this).parent().next().children('.dropdown-toggle').focus();
}
break;
case 38: // up key
e.preventDefault();
e.stopPropagation();
if($(this).parent().prev().length){
$(this).parent().prev().children('a').focus();
} else {
$(this).parents('ul').first().prev('.dropdown-toggle.toggled-on').focus();
}
if (($(this).is('ul.sub-menu .dropdown-toggle')) && ($(this).parent().prev().children('.dropdown-toggle').length)) {
$(this).parent().prev().children('.dropdown-toggle').focus();
}
break;
}
});
})(jQuery);
.menu-container:after {
display: table;
clear: both;
content: "";
}
.site-header-menu {
display: none;
font-size: 1rem;
clear: both;
}
.main-navigation ul {
margin: 0;
padding: 0;
list-style: none;
}
.main-navigation ul li {
margin-right: 40px;
margin-bottom: 10px;
min-height: 30px;
}
.main-navigation ul a,
.main-navigation ul a:visited {
border: none;
color: $black;
font-size: 16px;
font-weight: 400;
line-height: 22px;
position: relative;
text-decoration: none;
text-transform: uppercase;
}
.main-navigation ul a:hover {
border-bottom: 3px solid;
}
.main-navigation ul ul {
display: none;
margin-top: 0px;
margin-left: 25px;
}
.main-navigation ul ul li{
a{
font-size: 14px;
text-transform: none;
padding: 0;
margin-bottom: 10px;
border-bottom: solid 4px transparent;
padding-bottom: 2px;
}
&:last-child{
padding-bottom: 0;
a{
margin-bottom: 0;
}
}
&:hover > a {
background: $neutral_200;
}
}
.main-navigation ul ul ul {
display: none;
margin-left: 25px;
}
.no-js .site-header-menu,
.site-header-menu.toggled-on {
display: block;
}
.site-header-menu.toggled-on {
margin-top: 10px;
}
.no-js .main-navigation ul,
.main-navigation ul .sub-menu.toggled-on {
display: block;
}
button.dropdown-toggle,
button.menu-button {
display: inline;
background-color: transparent;
border: 0;
-webkit-appearance: none;
-moz-appearance: none;
cursor: pointer;
content: "";
}
button.dropdown-toggle {
width: 25px;
height: 25px;
position: absolute;
right: 15px;
margin-left: 10px;
padding: 2px;
}
.dropdown-toggle:after,
.dropdown-toggle.toggled-on:after {
font-size: 1rem;
}
.menu-button {
width: 25px;
height: 25px;
float: right;
padding: 0 !important;
font-size: 1.35rem;
margin-top: 25px;
padding: 5px 5px 5px 5px;
}
.main-navigation a:focus,
button.dropdown-toggle:focus,
button.menu-button:focus {
outline: 1px solid $black;
outline-offset: 2px;
}
.no-js .menu-button {
display: none;
}
/* Plus symbol to expand sub-menu on mobile */
.dropdown-toggle:after {
content: "\002B";
}
/* Minus symbol to collapse sub-menu on mobile */
.dropdown-toggle.toggled-on:after {
content: "\2212";
}
/* 'Hamburger' or bars to expand menu on mobile*/
.menu-button:before {
content: "\f0c9";
font-family: "Font Awesome 6 Free"; font-weight: 400;
}
/* Times (x) to collapse menu on mobile*/
.menu-button.toggled-on:before {
content: "\f00d";
font-family: "Font Awesome 6 Free"; font-weight: 400;
}
.dropdown-toggle:after,
.dropdown-toggle.toggled-on:after,
.menu-button:before,
.menu-button.toggled-on:before {
font-weight: bold;
}
/* Screen readers */
.screen-readers {
position: absolute !important;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
border: 0;
word-break: normal !important;
overflow: hidden;
clip: rect(0 0 0 0);
}
/* Desktop media query */
@media only screen and (min-width: 768px) {
button.menu-button {
display: none;
}
.menu-container {
padding-top: 0;
padding-bottom: 0;
padding-left: 0;
}
.site-header-menu {
display: block;
margin-left: 10px;
float: right;
//margin-top: 30px;
clear: none;
}
.main-navigation ul {
position: relative;
float: left;
}
.main-navigation ul li {
position: relative;
float: left;
margin: 0;
padding: 10px;
min-height: 0px;
}
.no-js .main-navigation ul ul,
.main-navigation ul ul {
position: absolute;
display: none;
top: 100%;
left: 0;
padding: 0;
z-index: 999;
background: $white;
border-radius: 15px;
padding: 25px;
}
.no-js .main-navigation ul ul li,
.main-navigation ul ul li {
float: none;
width: 175px;
padding: 0;
padding-bottom: 15px;
}
.main-navigation ul .has-sub-menu > li {
padding-right: 40px;
}
.no-js .main-navigation ul ul ul,
.main-navigation ul ul ul {
top: -1px;
left: 100%;
margin-left: 0;
margin-top: -5px;
}
ul.sub-menu .dropdown-toggle {
position: absolute;
right: 10px;
top: 4px;
}
/* Arrow down */
.main-navigation ul .dropdown-toggle:after {
content: "\f078";
font-family: "Font Awesome 6 Free"; font-weight: 400;
font-size: .75rem;
}
/* Arrow right */
.main-navigation ul ul .dropdown-toggle:after {
content: "\f054";
font-family: "Font Awesome 6 Free"; font-weight: 400;
font-size: .75rem;
}
/* Arrow up */
.main-navigation ul .dropdown-toggle.toggled-on:after {
content: "\f077";
font-family: "Font Awesome 6 Free"; font-weight: 400;
}
/* Arrow left */
.main-navigation ul ul .dropdown-toggle.toggled-on:after {
content: "\f053";
font-family: "Font Awesome 6 Free"; font-weight: 400;
}
.main-navigation ul .dropdown-toggle:after,
.main-navigation ul ul .dropdown-toggle:after,
.main-navigation ul .dropdown-toggle.toggled-on:after,
.main-navigation ul ul .dropdown-toggle.toggled-on:after {
font-weight: bold;
}
button.dropdown-toggle {
width: auto;
height: auto;
position: inherit;
right: auto;
}
.main-navigation ul li:hover > ul {
display: block;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment