<div class="dropdown__container" data-dropdown-container>
<button class="dropdown__btn" data-dropdown-toggle aria-haspopup="true" aria-expanded="false">
Dropdown 1
</button>
<div class="dropdown__panel" data-dropdown-panel>
<ul class="u-list-unstyled dropdown__list">
<li class="dropdown__item" role="none">
<a href="#" class="dropdown__link" role="menuitem" tabindex="-1">
This is an item
</a>
</li>
<li class="dropdown__item" role="none">
<a href="#" class="dropdown__link" role="menuitem" tabindex="-1">
This is an item
</a>
</li>
<li class="dropdown__item" role="none">
<a href="#" class="dropdown__link" role="menuitem" tabindex="-1">
This is an item
</a>
</li>
<li class="dropdown__item" role="none">
<a href="#" class="dropdown__link" role="menuitem" tabindex="-1">
This is an item
</a>
</li>
<li class="dropdown__item" role="none">
<a href="#" class="dropdown__link" role="menuitem" tabindex="-1">
This is an item
</a>
</li>
</ul>
</div>
</div>
<div class="dropdown__container" data-dropdown-container>
<button class="dropdown__btn" data-dropdown-toggle aria-haspopup="true" aria-expanded="false">
Dropdown 2
</button>
<div class="dropdown__panel" data-dropdown-panel>
<ul class="u-list-unstyled dropdown__list">
<li class="dropdown__item" role="none">
<a href="#" class="dropdown__link" role="menuitem" tabindex="-1">
This is an item
</a>
</li>
<li class="dropdown__item" role="none">
<a href="#" class="dropdown__link" role="menuitem" tabindex="-1">
This is an item
</a>
</li>
<li class="dropdown__item" role="none">
<a href="#" class="dropdown__link" role="menuitem" tabindex="-1">
This is an item
</a>
</li>
<li class="dropdown__item" role="none">
<a href="#" class="dropdown__link" role="menuitem" tabindex="-1">
This is an item
</a>
</li>
<li class="dropdown__item" role="none">
<a href="#" class="dropdown__link" role="menuitem" tabindex="-1">
This is an item
</a>
</li>
</ul>
</div>
</div>
{% for dropdown in dropdowns %}
<div class="dropdown__container" data-dropdown-container>
<button class="dropdown__btn" data-dropdown-toggle aria-haspopup="true" aria-expanded="false">
{{ dropdown.btnText }}
</button>
<div class="dropdown__panel" data-dropdown-panel>
<ul class="u-list-unstyled dropdown__list">
{% for item in dropdown.list %}
<li class="dropdown__item" role="none">
<a href="{{ item.href }}" class="dropdown__link" role="menuitem" tabindex="-1">
{{ item.text }}
</a>
</li>
{% endfor %}
</ul>
</div>
</div>
{% endfor %}
{
"dropdowns": [
{
"btnText": "Dropdown 1",
"list": [
{
"href": "#",
"text": "This is an item"
},
{
"href": "#",
"text": "This is an item"
},
{
"href": "#",
"text": "This is an item"
},
{
"href": "#",
"text": "This is an item"
},
{
"href": "#",
"text": "This is an item"
}
]
},
{
"btnText": "Dropdown 2",
"list": [
{
"href": "#",
"text": "This is an item"
},
{
"href": "#",
"text": "This is an item"
},
{
"href": "#",
"text": "This is an item"
},
{
"href": "#",
"text": "This is an item"
},
{
"href": "#",
"text": "This is an item"
}
]
}
]
}
.dropdown {
&__container {
position: relative;
display: inline-flex;
&.is-open {
.dropdown__panel {
top: 45px;
right: auto;
left: 0;
}
}
}
&__item {
margin-top: 0;
border-top: 1px solid color(neutral, lighter);
padding: 0;
background-color: color(default, light);
cursor: pointer;
text-align: left;
font-family: $brand-font;
&:first-child {
border-top: 0;
}
&:focus,
&:hover {
background-color: color(secondary, light);
}
}
&__panel {
z-index: z-index(dropdown) - 1;
position: absolute;
top: auto;
left: -9999px;
box-shadow: 0 10px 30px -10px rgba(color(default, dark), .07);
border: 1px solid color(neutral, lighter);
min-width: 230px;
background-color: color(default, light);
}
&__link {
display: inline-block;
padding: 12px 18px 10px;
width: 100%;
cursor: pointer;
text-align: left;
text-decoration: none;
font-family: inherit;
color: color(neutral, dark);
}
}
let dropdownContainers = document.querySelectorAll('[data-dropdown-container]');
for (let i = 0; i < dropdownContainers.length; i++) {
let dropdownToggle = dropdownContainers[i].querySelector('[data-dropdown-toggle]');
let dropdownPanel = dropdownContainers[i].querySelector('[data-dropdown-panel]');
let listLinks = dropdownContainers[i].querySelectorAll('.dropdown__link');
let dropdownStatus = false;
// Watch for clicks outside of the panel or toggle
document.addEventListener("click", (e) => {
let panelClicked = dropdownPanel.contains(e.target);
let toggleClicked = dropdownToggle.contains(e.target);
if (dropdownStatus && (!panelClicked && !toggleClicked)) {
closeDropdown(dropdownToggle, listLinks);
dropdownStatus = false;
}
})
// Watch for direct clicks on toggle
dropdownToggle.addEventListener("click", () => {
dropdownStatus = !dropdownStatus;
handleDropdown(dropdownToggle, listLinks, dropdownStatus);
});
document.addEventListener("keydown", (e) => {
if (!dropdownStatus && (e.keyCode == 13)) {
handleDropdown(dropdownToggle, listLinks, dropdownStatus);
}
});
document.addEventListener("keydown", (e) => {
if (dropdownStatus && (e.keyCode == 27)) {
closeDropdown(dropdownToggle, listLinks);
dropdownStatus = false;
}
});
for (let i = 0; i < listLinks.length; i++) {
listLinks[i].addEventListener("keydown", (e) => {
if (e.keyCode === 38) {
if (i === 0) {
listLinks[listLinks.length - 1].focus();
} else {
listLinks[i - 1].focus();
}
}
if (e.keyCode === 40) {
if (i === listLinks.length - 1) {
listLinks[0].focus();
} else {
listLinks[i + 1].focus();
}
}
});
};
};
let handleDropdown = (element, list, status) => {
let parent = element.parentNode;
if (status) {
parent.classList.add('is-open');
element.setAttribute("aria-expanded", "true");
for (let i = 0; i < list.length; i++) {
list[i].setAttribute("tabindex", "0");
}
} else {
parent.classList.remove('is-open');
element.setAttribute("aria-expanded", "false");
for (let i = 0; i < list.length; i++) {
list[i].setAttribute("tabindex", "-1");
}
}
}
let closeDropdown = (element, list) => {
element.parentNode.classList.remove('is-open');
element.setAttribute("aria-expanded", "false");
for (let i = 0; i < list.length; i++) {
list[i].setAttribute("tabindex", "-1");
}
}
Button dropdowns are used to toggle nested information. If a user clicks outside the element while the dropdown menu is open, the menu will close. If a user activates another dropdown menu while the dropdown menu is open, the active menu will close.
This button dropdown comes with the following configuration attributes:
data-dropdown-toggle
defines the button or element that will trigger the dropdown.data-dropdown-panel
defines the sub panel that contains all of the nested information.aria-haspopup="true"
.aria-expanded
set to true. If the dropdown menu is not visible, aria-expanded
is set to false.tabindex="0"
. If the dropdown menu is not visible, its menu items should have tabindex="-1"
.role="none"
, and each items inner link should have role=“menu item”
Enter
or Space
= Expand/Collapse dropdown menuTab
= If dropdown menu open, move to next focusable elementShift + Tab
= If dropdown menu open, move to previous focusable elementEsc
= Collapse dropdown menuWhen the button dropdown is focused, screen readers should announce the following:
When navigating through a button dropdown, the following tab order is expected: