Last active
April 11, 2019 16:47
-
-
Save reecelucas/42bde6270108de6298ee5ec1db026926 to your computer and use it in GitHub Desktop.
Accessible dropdown menu using the `<details>` element. https://codepen.io/reecelucas/pen/QozpGw
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<details class="c-menu" data-js-menu> | |
<summary id="menu-control" class="c-menu__control" data-js-menu-control> | |
Actions | |
</summary> | |
<ul id="menu" class="c-menu__list" data-js-menu-list> | |
<li> | |
<a href="#dowload">Download</a> | |
</li> | |
<li> | |
<a href="#copy">Copy to Clipboard</a> | |
</li> | |
<li> | |
<a href="#edit">Edit</a> | |
</li> | |
<li> | |
<a href="#delete">Delete</a> | |
</li> | |
<li> | |
<a href="#share">Share</a> | |
</li> | |
</ul> | |
</details> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* For accessibility requirements see: | |
* https://www.w3.org/TR/wai-aria-practices/examples/menu-button/menu-button-links.html | |
*/ | |
const menu = document.querySelector("[data-js-menu]"); | |
const menuControl = document.querySelector("[data-js-menu-control]"); | |
const menuList = document.querySelector("[data-js-menu-list]"); | |
const FOCUSABLE_ELEMENTS = "a, input, button, textarea, select, summary"; | |
const isFocusable = el => | |
!el.disabled && | |
!el.hidden && | |
(!el.type || el.type !== "hidden") && | |
!el.closest("[hidden]"); | |
const isActiveElement = el => el === document.activeElement; | |
const getFocusableChildren = el => | |
[...el.querySelectorAll(FOCUSABLE_ELEMENTS)].filter(isFocusable); | |
const getNextItem = (items, currIdx) => { | |
const nextIdx = currIdx + 1; | |
return nextIdx >= items.length ? items[0] : items[nextIdx]; | |
}; | |
const getPrevItem = (items, currIdx) => { | |
const prevIdx = currIdx - 1; | |
return prevIdx < 0 ? items[items.length - 1] : items[prevIdx]; | |
}; | |
const setAttributes = () => { | |
menuControl.setAttribute("aria-haspopup", "true"); | |
menuControl.setAttribute("aria-controls", menuList.id); | |
menuList.setAttribute("aria-role", "menu"); | |
menuList.setAttribute("aria-labelledby", menuControl.id); | |
}; | |
const selectMenuItem = key => { | |
const menuItems = getFocusableChildren(menuList); | |
const activeItem = menuItems.find(isActiveElement); | |
const activeItemIdx = menuItems.indexOf(activeItem); | |
const nextItem = | |
key === "ArrowDown" || key === "Down" | |
? getNextItem(menuItems, activeItemIdx) | |
: getPrevItem(menuItems, activeItemIdx); | |
nextItem.focus(); | |
}; | |
const onToggle = () => { | |
if (menu.open) { | |
const firstMenuItem = getFocusableChildren(menuList)[0]; | |
if (firstMenuItem) { | |
firstMenuItem.focus(); | |
} | |
} else { | |
menuControl.focus(); | |
} | |
}; | |
const onKeydown = event => { | |
if (!menu.open) { | |
if (isActiveElement(menuControl) && event.key === "ArrowDown") { | |
menu.open = true; | |
} | |
return; | |
} | |
switch (event.key) { | |
case "Tab": | |
event.preventDefault(); | |
break; | |
case "Escape": | |
case "Esc": // IE & Edge | |
menu.open = false; | |
break; | |
case "ArrowUp": | |
case "Up": // IE & Edge | |
case "ArrowDown": | |
case "Down": // IE & Edge | |
selectMenuItem(event.key); | |
} | |
}; | |
const init = () => { | |
if (!menu || !menuControl || !menuList) { | |
return; | |
} | |
setAttributes(); | |
menu.addEventListener("toggle", onToggle); | |
window.addEventListener("keydown", onKeydown); | |
}; | |
init(); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
.c-menu { | |
position: relative; | |
&[open] { | |
.c-menu__control { | |
&:before { | |
display: block; | |
} | |
} | |
} | |
} | |
.c-menu__control { | |
cursor: pointer; | |
&:before { | |
content: ""; | |
cursor: default; | |
display: none; | |
height: 100%; | |
left: 0; | |
position: fixed; | |
top: 0; | |
width: 100%; | |
z-index: 1; | |
} | |
} | |
.c-menu__list { | |
display: block; | |
position: absolute; | |
right: 0; | |
z-index: 2; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment