Skip to content

Instantly share code, notes, and snippets.

@AllThingsSmitty
Last active October 1, 2021 14:42
Show Gist options
  • Save AllThingsSmitty/628b80ba11de5ee91d67 to your computer and use it in GitHub Desktop.
Save AllThingsSmitty/628b80ba11de5ee91d67 to your computer and use it in GitHub Desktop.
Accessible dropdown menu
/* Top level nav */
.nav {
float: left;
margin: 20px 0;
}
/* Dropdowns */
.nav ul {
position: absolute;
top: 2.5em;
left: -9999px;
min-width: 150px;
opacity: 0;
transition: 0.1s linear opacity;
}
.nav li {
position: relative;
float: left;
}
/* Top level nav items */
.nav li > a {
float: left;
padding: 10px 15px;
text-decoration: none;
}
/* Top level hover state, preserve hover state when hovering dropdown */
.nav li > a:hover,
.nav li > a:focus,
.nav li:focus > a,
.nav li:hover > a {
outline: 0;
background: #EFEFEF;
}
.nav li:hover ul,
.nav li:focus ul,
ul.show-menu {
left: 0;
opacity: 0.99;
}
.nav ul li {
position: static;
float: none;
}
.nav ul a {
display: block;
float: none;
text-shadow: none;
font-size: 12px;
transition: 0.1s linear all;
}
.nav ul a:hover,
.nav ul a:focus {
text-shadow: none;
}
/*===========================
$Helpers
===========================*/
.list-reset {
margin: 0;
padding: 0;
list-style: none;
}
'use strict';
$(document).ready(function() {
// Setup the accesible nav
$('.nav').setup_navigation();
// RWD nav pattern
$('body').addClass('js');
var $menu = $('#menu'),
$menulink = $('.menu-link'),
$menuTrigger = $('.has-subnav > a');
$menulink.click(function(e) {
e.preventDefault();
$menulink.toggleClass('active');
$menu.toggleClass('active');
});
$menuTrigger.click(function(e) {
e.preventDefault();
var $this = $(this);
$this.toggleClass('active').next('ul').toggleClass('active');
});
});
/*
$(function(){
$('.nav').setup_navigation();
});
*/
var keyCodeMap = {
48: "0",
49: "1",
50: "2",
51: "3",
52: "4",
53: "5",
54: "6",
55: "7",
56: "8",
57: "9",
59: ";",
65: "a",
66: "b",
67: "c",
68: "d",
69: "e",
70: "f",
71: "g",
72: "h",
73: "i",
74: "j",
75: "k",
76: "l",
77: "m",
78: "n",
79: "o",
80: "p",
81: "q",
82: "r",
83: "s",
84: "t",
85: "u",
86: "v",
87: "w",
88: "x",
89: "y",
90: "z",
96: "0",
97: "1",
98: "2",
99: "3",
100: "4",
101: "5",
102: "6",
103: "7",
104: "8",
105: "9"
}
$.fn.setup_navigation = function(settings) {
settings = jQuery.extend({
menuHoverClass: 'show-menu'
}, settings);
// Add ARIA role to menubar and menu items
$(this).attr('role', 'menubar').find('li').attr('role', 'menuitem');
var top_level_links = $(this).find('> li > a');
// Added by Terrill: (removed temporarily: doesn't fix the JAWS problem after all)
// Add tabindex="0" to all top-level links
// Without at least one of these, JAWS doesn't read widget as a menu, despite all the other ARIA
//$(top_level_links).attr('tabindex','0');
// Set tabIndex to -1 so that top_level_links can't receive focus until menu is open
$(top_level_links).next('ul')
.attr('data-test', 'true')
.attr({
'aria-hidden': 'true',
'role': 'menu'
})
.find('a')
.attr('tabIndex', -1);
// Adding aria-haspopup for appropriate items
$(top_level_links).each(function() {
if ($(this).next('ul').length > 0)
$(this).parent('li').attr('aria-haspopup', 'true');
});
$(top_level_links).hover(function() {
$(this).closest('ul')
.attr('aria-hidden', 'false')
.find('.' + settings.menuHoverClass)
.attr('aria-hidden', 'true')
.removeClass(settings.menuHoverClass)
.find('a')
.attr('tabIndex', -1);
$(this).next('ul')
.attr('aria-hidden', 'false')
.addClass(settings.menuHoverClass)
.find('a').attr('tabIndex', 0);
});
$(top_level_links).focus(function() {
$(this).closest('ul')
// The following was adding aria-hidden="false" to root ul since menu is never hidden
// and seemed to be causing flakiness in JAWS (needs more testing)
// .attr('aria-hidden', 'false')
.find('.' + settings.menuHoverClass)
.attr('aria-hidden', 'true')
.removeClass(settings.menuHoverClass)
.find('a')
.attr('tabIndex', -1);
$(this).next('ul')
.attr('aria-hidden', 'false')
.addClass(settings.menuHoverClass)
.find('a').attr('tabIndex', 0);
});
// Bind arrow keys for navigation
$(top_level_links).keydown(function(e) {
if (e.keyCode == 37) {
e.preventDefault();
// This is the first item
if ($(this).parent('li').prev('li').length == 0) {
$(this).parents('ul').find('> li').last().find('a').first().focus();
} else {
$(this).parent('li').prev('li').find('a').first().focus();
}
} else if (e.keyCode == 38) {
e.preventDefault();
if ($(this).parent('li').find('ul').length > 0) {
$(this).parent('li').find('ul')
.attr('aria-hidden', 'false')
.addClass(settings.menuHoverClass)
.find('a').attr('tabIndex', 0)
.last().focus();
}
} else if (e.keyCode == 39) {
e.preventDefault();
// This is the last item
if ($(this).parent('li').next('li').length == 0) {
$(this).parents('ul').find('> li').first().find('a').first().focus();
} else {
$(this).parent('li').next('li').find('a').first().focus();
}
} else if (e.keyCode == 40) {
e.preventDefault();
if ($(this).parent('li').find('ul').length > 0) {
$(this).parent('li').find('ul')
.attr('aria-hidden', 'false')
.addClass(settings.menuHoverClass)
.find('a').attr('tabIndex', 0)
.first().focus();
}
} else if (e.keyCode == 13 || e.keyCode == 32) {
// If submenu is hidden, open it
e.preventDefault();
$(this).parent('li').find('ul[aria-hidden=true]')
.attr('aria-hidden', 'false')
.addClass(settings.menuHoverClass)
.find('a').attr('tabIndex', 0)
.first().focus();
} else if (e.keyCode == 27) {
e.preventDefault();
$('.' + settings.menuHoverClass)
.attr('aria-hidden', 'true')
.removeClass(settings.menuHoverClass)
.find('a')
.attr('tabIndex', -1);
} else {
$(this).parent('li').find('ul[aria-hidden=false] a').each(function() {
if ($(this).text().substring(0, 1).toLowerCase() == keyCodeMap[e.keyCode]) {
$(this).focus();
return false;
}
});
}
});
var links = $(top_level_links).parent('li').find('ul').find('a');
$(links).keydown(function(e) {
if (e.keyCode == 38) {
e.preventDefault();
// This is the first item
if ($(this).parent('li').prev('li').length == 0) {
$(this).parents('ul').parents('li').find('a').first().focus();
} else {
$(this).parent('li').prev('li').find('a').first().focus();
}
} else if (e.keyCode == 40) {
e.preventDefault();
if ($(this).parent('li').next('li').length == 0) {
$(this).parents('ul').parents('li').find('a').first().focus();
} else {
$(this).parent('li').next('li').find('a').first().focus();
}
} else if (e.keyCode == 27 || e.keyCode == 37) {
e.preventDefault();
$(this)
.parents('ul').first()
.prev('a').focus()
.parents('ul').first().find('.' + settings.menuHoverClass)
.attr('aria-hidden', 'true')
.removeClass(settings.menuHoverClass)
.find('a')
.attr('tabIndex', -1);
} else if (e.keyCode == 32) {
e.preventDefault();
window.location = $(this).attr('href');
} else {
var found = false;
$(this).parent('li').nextAll('li').find('a').each(function() {
if ($(this).text().substring(0, 1).toLowerCase() == keyCodeMap[e.keyCode]) {
$(this).focus();
found = true;
return false;
}
});
if (!found) {
$(this).parent('li').prevAll('li').find('a').each(function() {
if ($(this).text().substring(0, 1).toLowerCase() == keyCodeMap[e.keyCode]) {
$(this).focus();
return false;
}
});
}
}
});
// Hide menu if click or focus occurs outside of navigation
$(this).find('a').last().keydown(function(e) {
if (e.keyCode == 9) {
// If the user tabs out of the navigation hide all menus
$('.' + settings.menuHoverClass)
.attr('aria-hidden', 'true')
.removeClass(settings.menuHoverClass)
.find('a')
.attr('tabIndex', -1);
}
});
$(document).click(function() {
$('.' + settings.menuHoverClass).attr('aria-hidden', 'true').removeClass(settings.menuHoverClass).find('a').attr('tabIndex', -1);
});
$(this).click(function(e) {
e.stopPropagation();
});
};
<!-- Based on @grayghostvisuals' accessible dropdown menu -->
<nav class="menu" id="a11y-menu" role="navigation" aria-label="Main menu">
<ul class="nav level-1 list-reset" role="menubar" aria-hidden="false">
<li class="has-subnav" role="menuitem" aria-haspopup="true">
<a href="#about">About</a>
<ul class="level-2 list-reset" data-test="true" aria-hidden="true" role="menu">
<li role="menuitem"><a href="#news" tabindex="-1">News</a></li>
<li role="menuitem"><a href="#governance" tabindex="-1">Governance</a></li>
<li role="menuitem"><a href="#diversity" tabindex="-1">Diversity</a></li>
<li role="menuitem"><a href="#contact" tabindex="-1">ContactUs</a></li>
</ul>
</li>
<li role="menuitem" aria-haspopup="true">
<a href="#academics">Academics</a>
<ul class="level-2 list-reset" data-test="true" aria-hidden="true" role="menu" class="list-reset">
<li role="menuitem"><a href="#programs" tabindex="-1">Degree Programs</a></li>
<li role="menuitem"><a href="#faculty" tabindex="-1">Faculty</a></li>
<li role="menuitem"><a href="#distance" tabindex="-1">Distance Learning</a></li>
<li role="menuitem"><a href="#libs" tabindex="-1">Libraries</a></li>
</ul>
</li>
<li role="menuitem" aria-haspopup="true">
<a href="#admissions">Admissions</a>
<ul class="level-2 list-reset" data-test="true" aria-hidden="true" role="menu" class="list-reset">
<li role="menuitem"><a href="#undergraduate" tabindex="-1">Undergraduate</a></li>
<li role="menuitem"><a href="#tuition" tabindex="-1">Tuition</a></li>
<li role="menuitem"><a href="#financial-aid" tabindex="-1">Financial Aid</a></li>
</ul>
</li>
<li role="menuitem" aria-haspopup="true">
<a href="#visitors">Visitors</a>
<ul class="level-2 list-reset" data-test="true" aria-hidden="true" role="menu" class="list-reset">
<li role="menuitem"><a href="#events" tabindex="-1">Events</a></li>
<li role="menuitem"><a href="#campus-map" tabindex="-1">Campus Map</a></li>
<li role="menuitem"><a href="#parking" tabindex="-1">Parking</a></li>
</ul>
</li>
</ul>
</nav>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment