Popover Api for menu

by Julien Roche

HTML

<div class="menu">
  <button popovertarget="mainpopover">Menu</button>
  <div id="mainpopover" popover="auto" class="menu__mainpopover">
    <nav class="menu__listcontainer">
      <a href="#">Home</a>
      <div class="menu__subcontainer">
        <a href="#" tabindex="0">Pizza</a>
        <div id="subpopover" popover="auto">
          <div class="menu__listcontainer">
            <a href="#">Margherita</a>
            <a href="#">Pepperoni</a>
            <a href="#">Ham &amp; Shroom</a>
            <a href="#">Vegan</a>
          </div>
        </div>
      </div>
      <a href="#">Music</a>
      <a href="#">Wombats</a>
      <a href="#">Finland</a>
      <div class="menu__subcontainer">
        <a href="#" tabindex="0">Pizza</a>
        <div id="subpopover" popover="auto">
          <div class="menu__listcontainer">
            <a href="#">Margherita</a>
            <a href="#">Pepperoni</a>
            <a href="#">Ham &amp; Shroom</a>
            <a href="#">Vegan</a>
          </div>
        </div>
      </div>
    </nav>
  </div>
</div>

CSS

body,
html {
  font-family: sans-serif;
}

.menu {
  display: inline-block;

  button {
    anchor-name: --menu;
  }

  .menu__mainpopover {
    border: 1px solid black;
    width: 120px;
    inset: unset;

    top: calc(anchor(self-end) + 0.25rem); /* === anchor(bottom) */
    left: anchor(self-start); /* === anchor(left) */
    position-anchor: --menu;

    & .menu__listcontainer {
      display: flex;
      flex-direction: column;
    }

    .menu__subcontainer {
      display: flex;
      flex-direction: column;

      &.menu__subcontainer--active {
        anchor-name: --submenu;
      }

      & > a {
        position: relative;

        &::after {
          content: '➤';
          font-weight: bold;
          position: absolute;
          top: 0;
          right: 0.5rem;
        }
      }

      & [popover] {
        border: 1px solid black;
        width: 120px;
        inset: unset;

        top: anchor(self-start); /* === anchor(top) */
        left: calc(anchor(self-end) - 1rem); /* === anchor(right) */
        position-anchor: --submenu;
      }
    }

    a {
      flex: 1;
      text-decoration: none;
      outline: none;
      text-align: center;
      line-height: 3;
      color: black;

      &:link,
      &:visited {
        background: palegoldenrod;
        color: black;
      }

      &:hover,
      &:focus {
        background: orange;
      }

      &:active {
        background: darkred;
        color: white;
      }
    }
  }
}

JavaScript

const mainpopover = document.querySelector('.menu__mainpopover');
const subcontainers = Array.from(document.querySelectorAll('.menu__subcontainer'));

subcontainers.forEach((subcontainer) => {
  const subpopover = subcontainer.querySelector('[popover]');

  [
    'pointerover', // Event to show the subpopover when the mouse moves over
    'focusin', // Event to make the subpopover keyboard-accessible
  ].forEach((eventName) => {
    subcontainer.addEventListener(eventName, (event) => {
      subcontainer.classList.add('menu__subcontainer--active');
      event.stopPropagation();
      subpopover.showPopover();
    });
  });

  // Event to hide the subpopover when the mouse moves out
  subcontainer.addEventListener('pointerout', () => {
    subcontainer.classList.remove('menu__subcontainer--active');
    subpopover.hidePopover();
  });
});

// Events to hide the popover menus when an option is selected in either of them
mainpopover.addEventListener('pointerdown', () => {
  subcontainers.forEach((subcontainer) => {
    const subpopover = subcontainer.querySelector('[popover]');
    subcontainer.classList.remove('menu__subcontainer--active');
    subpopover.hidePopover();
  });

  mainpopover.hidePopover();
});

mainpopover.addEventListener('focusin', () => {
  subcontainers.forEach((subcontainer) => {
    const subpopover = subcontainer.querySelector('[popover]');
    subcontainer.classList.remove('menu__subcontainer--active');
    subpopover.hidePopover();
  });
});