<div class="menu" tabindex="0">
<a href="https://google.com" target="_blank" tabindex="-1">Link to Google</a>
<a href="#" tabindex="-1">Link</a>
<a href="#" tabindex="-1">Link</a>
<a href="#" tabindex="-1">Link</a>
<a href="#" tabindex="-1">Link</a>
</div>
<div class="menu" tabindex="0">
<a href="#" tabindex="-1">Link</a>
<a href="#" tabindex="-1">Link</a>
<a href="#" tabindex="-1">Link</a>
<a href="#" tabindex="-1">Link</a>
<a href="#" tabindex="-1">Link</a>
</div>
<p><kbd>Tab</kbd> / <kbd>Shift + Tab</kbd> to switch between the menus, <kbd>🠕</kbd> and <kbd>🠗</kbd> to go up and down inside the menu. <kbd>Space</kbd> to click a link.</p>
* {
padding: 0;
margin: 0;
color: white;
box-sizing: border-box;
font-family: system-ui, system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
}
body {
display: grid;
grid-auto-flow: column;
max-width: 40rem;
margin-inline: auto;
place-items: center;
background-color: #111;
height: 100vh;
}
.menu {
background-color: ;
display: flex;
flex-direction: column;
border: 1px solid #555;
border-radius: 0.5rem;
overflow: hidden;
}
.menu > a {
padding-inline: 4rem;
padding-block: 1rem;
}
.menu > * + * {
border-top: 1px solid #555;
}
.menu:focus-within {
outline: 0.2rem solid #666;
outline-offset: 0.25rem;
}
a {
text-decoration: none;
text-align: center;
}
a:hover, a:focus {
background-color: #444850;
outline: none;
text-decoration: #aaa underline 0.2rem;
}
kbd {
padding: 0.5rem;
margin-inline: 0.2rem;
border-radius: 0.3rem;
font-family: monospace;
border: 0.5px solid #666;
}
p {
position: absolute;
left: 2rem;
top: 2rem;
line-height: 2.5rem;
}
const menuList = document.querySelectorAll(".menu");
menuList.forEach((menu) => {
const children = Array.from(menu.children);
let current = 0;
const handleKeyDown = (e) => {
if (!(["ArrowUp", "ArrowDown", "Space"].includes(e.code))) return;
if(e.code === "Space") {
children[current].click();
return;
}
const selected = children[current];
selected.setAttribute("tabindex", -1);
let next;
if (e.code === "ArrowDown") {
next = current + 1;
if (current == children.length - 1) {
next = 0;
}
} else if ((e.code = "ArrowUp")) {
next = current - 1;
if (current == 0) {
next = children.length - 1;
}
}
children[next].setAttribute("tabindex", 0);
children[next].focus();
current = next;
};
menu.addEventListener("focus", (e) => {
if (children.length > 0) {
menu.setAttribute("tabindex", -1);
children[current].setAttribute("tabindex", 0);
children[current].focus();
}
menu.addEventListener("keydown", handleKeyDown);
});
menu.addEventListener("blur", (e) => {
menu.removeEventListener("keydown", handleKeyDown);
});
});
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.