Accordion component keyboard commands



I have an accordion component which is working correctly using the ‘tab’ to navigate through the controls, and on ‘enter’ and ‘spacebar’ the accordion expands. I am trying to figure out a way to navigate to the next accordion using the ‘up’ and ‘down’ arrow keys. I am familiar with Javascript but I have not been able to achieve this with my existing code. Any help I can get is greatly appreciated.

Here’s a CodePen to my accordion component. https://codepen.io/ozman2182/pen/vYgvGOd

(function () {
  const headings = document.querySelectorAll(".unr-accordion--heading");

  Array.prototype.forEach.call(headings, (h) => {
    let btn = h.querySelector("button");
    let target = h.nextElementSibling;

    btn.onclick = () => {
      let expanded = btn.getAttribute("aria-expanded") === "true";

      btn.setAttribute("aria-expanded", !expanded);
      target.hidden = expanded;
    };
  });
})();

Answer

Excuse the fact that my JS is old school – I still support IE9 😋

In the example at the end of this answer I have added the code required to make the arrow keys cycle up and down through the list (and loop around).

I have also added Home and End keys to go to the start and end of the list (as that is expected behaviour).

In summary we:

  • grab all the buttons with .querySelectorAll('.unr-accordion--heading>button');
  • add an event listener for “keydown”
  • see if the keyCode is 38 (up) or 40 (down), if it is we apply a “direction” of -1 (to go up one item in our list of buttons) or +1 (to go down).
  • if the keyCode is 36 (“Home”) – we set a direction of -999 so we can check it later
  • if the keyCode is 35 (“End”) – we set a direction of 999 so we can check it later
  • if a direction is set (up or down arrow was pressed or home / end) we then loop through all the buttons.
  • if the button in the current stage of the loop equals document.activeElement (the currently focused item) then we know we are both in the accordion and on a button and the arrow keys should function.
  • We then check if the direction is up and we are focused on the first item in the buttons list (direction == -1 && x == 0) or if the “direction” is -999 (the home key), so we can loop to the bottom of the list of buttons and focus that one. We exit the loop if so with break;
  • If not we then check if the direction is down and we are focused on the last item in the buttons list (direction == 1 && x == max) or if the “direction” is +999 (the end key), so we can loop to the top of the list of buttons and focus that one. We exit the loop if so with break;
  • finally if neither of the above are true we just move focus by the direction (-1 for up, +1 for down) and then exit the loop.

(function () {
  const headings = document.querySelectorAll(".unr-accordion--heading");

  Array.prototype.forEach.call(headings, (h) => {
    let btn = h.querySelector("button");
    let target = h.nextElementSibling;

    btn.onclick = () => {
      let expanded = btn.getAttribute("aria-expanded") === "true";

      btn.setAttribute("aria-expanded", !expanded);
      target.hidden = expanded;
    };
  });
  
    var btns = document.querySelectorAll('.unr-accordion--heading>button');
  
    document.addEventListener('keydown', function(e){
      var direction = 0;
      var max = btns.length - 1;
      direction = (e.keyCode == 38) ? -1 : direction;
      direction = (e.keyCode == 40) ? 1 : direction;
      direction = (e.keyCode == 35) ? -999 : direction;
      direction = (e.keyCode == 36) ? 999 : direction;
      
      if(direction != ""){
         e.preventDefault();
        for(x = 0; x <= max; x++){
            if(document.activeElement == btns[x]){
              if(direction == -1 && x == 0 || direction == -999){
                  btns[max].focus();
                  break;
              }
              if(direction == 1 && x == max || direction == 999){
                  btns[0].focus();
                  break;
              }
              btns[x + direction].focus();
              break;
            }
        }
      }
      
      
    })
  
  
})();
:root {
  --blue-10: #E6E9EC;
  --blue-20: #CDD2D9;
  --blue-50: #828FA1;
  --blue-80: #364B68;
  --blue-100: #041E42;
}
html {
  font-family: Helvetica, sans-serif;
  color: var(--blue-100);
}
section {
  max-width: 920px;
  margin-top: 3em;
  margin-right: auto;
  margin-left: auto;
}

.unr-accordion--expandall {
  margin-bottom: 1em;
  border:2px solid var(--blue-20);
  border-radius: 5px;
  padding: 0.5em 1em;
  background-color: white;
}

.unr-accordion--expandall:hover,
.unr-accordion--expandall:focus {
  border:2px solid var(--blue-10);
  background-color: var(--blue-10);
}

.unr-accordion--wrapper {
  border: 2px solid var(--blue-20);
  border-radius: 5px;
  margin-bottom: 0.5em;
}

.unr-accordion--wrapper:last-child {
  margin-bottom: 0;
}

.unr-accordion--wrapper > h2 {
  display: flex;
  margin: 0;
  border-radius: 5px;
}

.unr-accordion--wrapper > h2 button {
  all: inherit;
  border: 0;
  display: flex;
  justify-content: space-between;
  align-items: center;
  width: 100%;
  margin: 0;
  padding: 0.5em;
  font-size: 1.5rem;
  line-height: 1.5;
}

.unr-accordion--wrapper > h2 button:hover {
  background-color: var(--blue-10);
}

.unr-accordion--wrapper > h2 button svg {
  font-size: 1rem;
  margin-left: 0.5em;
  flex-shrink: 0;
}

.unr-accordion--wrapper > h2 button:focus svg {
  outline: 2px solid;
}

.unr-accordion--wrapper > h2 button[aria-expanded="true"] {
  background-color: var(--blue-10);
}

.unr-accordion--wrapper > h2 button[aria-expanded="true"] .vert {
  display: none;
}

.unr-accordion--wrapper > h2 button[aria-expanded] rect {
  fill: currentColor;
}

.unr-accordion--panel {
  margin-top: -1em;
  padding-top: 1em;
  padding-right: 1em;
  padding-bottom: 1em;
  padding-left: 1em;
  background-color: var(--blue-10);
}
<section>
<h1>Edgar Allan Poe was an American writer, poet, editor, and literary critic.</h1>
<p>Poe is best known for his poetry and short stories, particularly his tales of mystery and the macabre. He is widely regarded as a central figure of Romanticism in the United States and of American literature as a whole, and he was one of the country's earliest practitioners of the short story.</p>

<!-- <button class="unr-accordion--expandall" href="#">Expand All</button> -->

<!-- accordion items -->
<div class="unr-accordions">
  <div class="unr-accordion--wrapper">
    <h2 class="unr-accordion--heading">
      <button aria-expanded="false">
        The Black Cat (short story)
        <svg viewbox="0 0 10 10" width="24px" height="24px" aria-hidden="true" focusable="false">
          <rect class="vert" height="8" width="2" y="1" x="4" />
          <rect height="2" width="8" y="4" x="1" />
        </svg>
      </button>
    </h2>
    <div class="unr-accordion--panel" hidden>
      <p>It was first published in the August 19, 1843, edition of The Saturday Evening Post. In the story, an unnamed narrator has a strong affection for pets until he perversely turns to abusing them.</p>
    </div>
  </div>

  <div class="unr-accordion--wrapper">
    <h2 class="unr-accordion--heading">
      <button aria-expanded="false">
        The Cask of Amontillado
        <svg viewbox="0 0 10 10" width="24px" height="24px" aria-hidden="true" focusable="false">
          <rect class="vert" height="8" width="2" y="1" x="4" />
          <rect height="2" width="8" y="4" x="1" />
        </svg>
      </button>
    </h2>
    <div class="unr-accordion--panel" hidden>
      <p>First published in the November 1846 issue of Godey's Lady's Book. The story, set in an unnamed Italian city at carnival time in an unspecified year, is about a man taking fatal revenge on a friend who, he believes, has insulted him. Like several of Poe's stories, and in keeping with the 19th-century fascination with the subject, the narrative revolves around a person being buried alive – in this case, by immurement. As in "The Black Cat" and "The Tell-Tale Heart", Poe conveys the story from the murderer's perspective.</p>
    </div>
  </div>

  <div class="unr-accordion--wrapper">
    <h2 class="unr-accordion--heading">
      <button aria-expanded="false">
        The Gold-Bug
        <svg viewbox="0 0 10 10" width="24px" height="24px" aria-hidden="true" focusable="false">
          <rect class="vert" height="8" width="2" y="1" x="4" />
          <rect height="2" width="8" y="4" x="1" />
        </svg>
      </button>
    </h2>
    <div class="unr-accordion--panel" hidden>
      <p>The plot follows William Legrand, who was bitten by a gold-colored bug. His servant Jupiter fears that Legrand is going insane and goes to Legrand's friend, an unnamed narrator, who agrees to visit his old friend. Legrand pulls the other two into an adventure after deciphering a secret message that will lead to a buried treasure. </p>
    </div>
  </div>
</div>
</div>
<!-- end: accordion component -->
</section>


Source: stackoverflow