<div class="Container">
      <nav id="Site-Navigation" class="Navigation" aria-label="Main Navigation">
        <ul class="NavigationList javascript-disabled">
          <li class="NavigationList-listItem has-submenu">
            <a
              href="#"
              class="NavigationList-listItem-listLink"
              aria-haspopup="true"
              aria-expanded="false"
            >
              Menu 1
              <svg aria-hidden="true" width="20" height="20">
                <use xlink:href="#arrow" />
              </svg>
            </a>
            <div class="Submenu">
              <h2 class="Submenu-header">Menu 1</h2>
              <ul class="SubmenuList">
                <li class="SubmenuList-listItem">
                  <a href="#" class="SubmenuList-listItem-listLink">Item 1</a>
                </li>
                <li class="SubmenuList-listItem">
                  <a href="#" class="SubmenuList-listItem-listLink">Item 2</a>
                </li>
                <li class="SubmenuList-listItem">
                  <a href="#" class="SubmenuList-listItem-listLink">Item 3</a>
                </li>
              </ul>
            </div>
          </li>
          <li class="NavigationList-listItem has-submenu">
            <a
              href="#"
              class="
                NavigationList-listItem-listLink
                NavigationList-listItem-listLink--whiteLeftBorder
              "
              aria-haspopup="true"
              aria-expanded="false"
            >
              Menu 2
              <svg aria-hidden="true" width="20" height="20">
                <use xlink:href="#arrow" />
              </svg>
            </a>
            <div class="Submenu">
              <h2 class="Submenu-header">Menu 2</h2>
              <ul class="SubmenuList">
                <li class="SubmenuList-listItem">
                  <a href="#" class="SubmenuList-listItem-listLink">Item 4</a>
                </li>
                <li class="SubmenuList-listItem">
                  <a href="#" class="SubmenuList-listItem-listLink">Item 5</a>
                </li>
                <li class="SubmenuList-listItem">
                  <a href="#" class="SubmenuList-listItem-listLink">Item 6</a>
                </li>
              </ul>
            </div>
          </li>
        </ul>
      </nav>
    </div>

    <!-- SVG to be used in menu link/button toggle -->
    <svg xmlns="http://www.w3.org/2000/svg" hidden>
      <symbol id="arrow" viewbox="0 0 16 16">
        <polyline
          points="4 6, 8 10, 12 6"
          stroke="#fff"
          stroke-width="2"
          fill="transparent"
          stroke-linecap="round"
        />
      </symbol>
    </svg>

    <script src="flyout.js"></script>
// Frontline Reset
*{font-size:inherit;line-height:inherit;margin:0;padding:0;vertical-align:baseline}*,:after,:before{box-sizing:inherit}html{box-sizing:border-box;-ms-overflow-style:-ms-autohiding-scrollbar;overflow-y:scroll;-webkit-text-size-adjust:100%}article,aside,details,figcaption,figure,footer,header,main,menu,nav,section,summary{display:block}summary{display:list-item}audio,canvas,progress,video{display:inline-block}audio:not([controls]){display:none;height:0}progress{vertical-align:baseline}[hidden],template{display:none}[aria-busy=true]{cursor:progress}h1,h2,h3,h4,h5,h6{font-weight:400}[tabindex],a,area,button,input,label,select,summary,textarea{touch-action:manipulation}img{border:0;height:auto;max-width:100%}ol,ul{list-style:none}table{border-collapse:collapse;border-spacing:0}embed,iframe,object{display:block;max-width:100%;position:relative;z-index:1}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}blockquote[type=cite]{border:0}address,cite,dfn,var{font-style:normal}abbr[title]{border-bottom:0;text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted}ins{text-decoration:none}hr{box-sizing:content-box;height:0;overflow:visible}a{background-color:transparent}a[href^=mailto]{word-break:break-all;word-break:break-word}a[href^=tel]{color:inherit;text-decoration:none}a>svg,button>svg{pointer-events:none}button,input,select,textarea{font:inherit}optgroup{font-weight:700}button,select{text-transform:none}button,input,select{overflow:visible}select::-ms-value{color:currentColor}optgroup{font-weight:700}fieldset{border:0}legend{border:0;box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}textarea{border-radius:0;display:block;overflow:auto;width:100%}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[aria-controls],[type=button],[type=reset],[type=submit],[type=checkbox],[type=radio],button,label,select{cursor:pointer}[readonly]{cursor:text}[aria-busy=true]{cursor:progress}[aria-disabled=true],[disabled]{cursor:not-allowed}button::-moz-focus-inner,input::-moz-focus-inner{padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}[type=search],[type=tel],[type=text],[type=url],[type=email],[type=number],[type=password]{border-radius:0;-webkit-appearance:none}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{border-radius:0}[type=search]::-webkit-search-decoration,[type=search]::-webkit-search-results-button,[type=search]::-webkit-search-results-decoration{-webkit-appearance:none}::-moz-placeholder{opacity:1}
// End Frontline Reset

// Variables
$background: #ededed;
$flyout-background: #ffffff;
$flyout-border: #000000;
$flyout-linkcolor: blue;
$base: #11017b;
$active: #49875a;
$navigation-linkcolor: #ffffff;
// End Variables

// Hide the base SVG
svg[hidden] {
  display: none;
  position: absolute;
}

// Typography
body {
  font-family: "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue",
    Helvetica, Arial, "Lucida Grande", sans-serif;
  font-weight: 300;
}
// End Typography

div.Container {
  width: 100vw;
  height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
}

ul.NavigationList {
  background-color: $background;
  display: flex;
  width: 550px;
  height: 300px;
  li.NavigationList-listItem {
    flex: 1;
    a.NavigationList-listItem-listLink,
    button.NavigationList-listItem-listLink {
      width: 100%;
      text-align: center;
      display: block;
      padding: 20px;
      border: 1px solid $base;
      font-weight: 700;
      font-size: 20px;
      text-decoration: none;
      background-color: $base;
      color: $navigation-linkcolor;
      &:hover {
        cursor: pointer;
      }
      &:hover,
      &:focus-within {
        background-color: $active;
        border: 1px solid $active;
      }
    }
    a.NavigationList-listItem-listLink[aria-expanded="true"],
    button.NavigationList-listItem-listLink[aria-expanded="true"] {
      background-color: $active;
      border: 1px solid $active;
    }
    a.NavigationList-listItem-listLink--whiteLeftBorder,
    a.NavigationList-listItem-listLink--whiteLeftBorder:hover,
    a.NavigationList-listItem-listLink--whiteLeftBorder:focus-within,
    button.NavigationList-listItem-listLink--whiteLeftBorder,
    button.NavigationList-listItem-listLink--whiteLeftBorder:hover,
    button.NavigationList-listItem-listLink--whiteLeftBorder:focus-within {
      border-left: 1px solid $navigation-linkcolor;
    }
  }
}

div.Submenu {
  background: $flyout-background;
  border: 2px solid $flyout-border;
  padding: 10px 30px;
  h2,
  ul li {
    padding: 10px 0;
    a {
      color: $flyout-linkcolor;
    }
  }
  h2 {
    font-weight: 700;
  }
}

// No JavaScript Hover/Focus Fallback
nav.Navigation > ul.javascript-disabled li.has-submenu {
  height: 0;
  div.Submenu {
    display: none;
  }
  &:hover,
  &:focus-within {
    height: 100%;
    div.Submenu {
      display: block;
    }
  }
}
// End No JavaScript Hover/Focus Fallback

// JavaScript Toggle
nav.Navigation > ul li.has-submenu div[aria-hidden="true"] {
  display: none;
}

nav.Navigation > ul li.has-submenu.open div[aria-hidden="false"] {
  display: block;
}

li.has-submenu a,
li.has-submenu button {
  position: relative;
  svg {
    width: 20px;
    height: 20px;
    position: absolute;
    top: 50%;
    right: 80px;
    transform: translateY(-50%);
    margin-left: 8px;
  }
}

li.has-submenu a[aria-expanded="true"] svg,
li.has-submenu button[aria-expanded="true"] svg {
  transform: translateY(-50%) scaleY(-1);
}
View Compiled
/**
 * IIFE that:
 * 1. listens for DOMContentLoaded,
 * 2. instantiates a new FlyoutMenu, and
 * 3. calls `flyoutMenu.init()`
 *
 * Main inspiration/guides for this code come from:
 * - Fly-out Menus on W3.org (https://www.w3.org/WAI/tutorials/menus/flyout/)
 * - Click Menu by Mark Root-Wily on CSS Tricks (https://css-tricks.com/in-praise-of-the-unambiguous-click-menu/)
 */
(function () {
  "use strict";

  /**
   * Represents the flyout menus.
   * @param {Object} menu - Parent unordered list `ul.NavigationList`
   */
  const FlyoutMenu = function (menu) {
    /**
     * Variable initialization:
     * - container: Navigation element `nav#Site-Navigation.Navigation`
     * - currentMenu: Initially unassigned, given value on submenu toggle
     */
    const container = menu.parentElement;
    let currentMenu;

    /**
     * Flyout menu initialization that:
     * 1. calls `menuSetup()`;
     * 2. adds click event listener to call `closeOpenMenu`
     */
    this.init = function () {
      menuSetup();
      document.addEventListener("click", closeOpenMenu);
    };

    /**
     * Closes an open menu when click target is outside of container
     * @param {Object} e - Click event
     */
    function closeOpenMenu(e) {
      if (currentMenu && !e.target.closest(`#${container.id}`)) {
        toggleSubmenu(currentMenu);
      }
    }

    /**
     * Menu setup process:
     * 1. Removes "javascript-disabled" class from `ul.NavigationList`
     * 2. Iterates each `div.Submenu` to:
     *    1. Convert parent element `a` to `button`
     *    2. Set up proper aria attributes on `button`
     *    3. Add a click event listener to button to call `handleclick`
     *    4. Add a keyup event listener to `nav#Site-Navigation.Navigation` to call `handleKeyUp`
     */
    function menuSetup() {
      menu.classList.remove("javascript-disabled");
      menu.querySelectorAll("div.Submenu").forEach((submenu) => {
        const menuItem = submenu.parentElement;
        if (typeof submenu !== undefined) {
          const button = convertLinkToButton(menuItem);
          setUpAria(submenu, button);
          button.addEventListener("click", handleClick);
          menu.addEventListener("keyup", handleKeyUp);
        }
      });
    }

    /**
     * Converts a link to a button
     * @param {Object} menuItem - List item that contains link `li.NavigationList-listItem`
     * @returns Button with text content and all attributes from original link except href
     *
     * Guide for Link/Button Enhancement on justmarkup.com(https://justmarkup.com/articles/2019-01-21-the-link-to-button-enhancement/)
     */
    function convertLinkToButton(menuItem) {
      const link = menuItem.getElementsByTagName("a")[0];
      const { innerHTML, attributes } = link;
      const button = document.createElement("button");
      if (link !== null) {
        button.innerHTML = innerHTML.trim();
        for (let i = 0; i < attributes.length; i += 1) {
          let attr = attributes[i];
          if (attr.name !== "href") {
            button.setAttribute(attr.name, attr.value);
          }
        }
        menuItem.replaceChild(button, link);
      }
      return button;
    }

    /**
     * Sets up Aria attributes and controls for submenu and their respective buttons
     * @param {Object} submenu - `div.Submenu`
     * @param {Object} button - `button.NavigationList-listItem-listLink`
     */
    function setUpAria(submenu, button) {
      const submenuId = submenu.getAttribute("id");
      let id;
      if (submenuId === null) {
        id = `${button.textContent
          .trim()
          .replace(/\s+/g, "-")
          .toLowerCase()}-submenu`;
      } else {
        id = `${submenuId}-submenu`;
      }
      button.setAttribute("aria-controls", id);
      button.setAttribute("aria-expanded", false);
      submenu.setAttribute("id", id);
      submenu.setAttribute("aria-hidden", true);
    }

    /**
     * Handles clicks on menu toggle button:
     * 1. Check if there's a currently open menu that we didn't click on, toggle that closed
     * 2. Call `toggleSubmenu` for the clicked button
     * @param {Object} e - Click event
     */
    function handleClick(e) {
      const button = e.currentTarget;
      if (currentMenu && button !== currentMenu) {
        toggleSubmenu(currentMenu);
      }
      toggleSubmenu(button);
    }

    /**
     * Handles keyup event if focus is in `ul.NavigationList`
     * 1. If esc key was keyuped:
     *    1. If closest target is an expanded submenu:
     *        1. Put focus on the button that controls it
     *        2. Toggle the open menu closed
     *    2. If target is a button with `aria-expanded`:
     *        1. Toggle its open menu closed
     * @param {Object} e - Click event
     */
    function handleKeyUp(e) {
      if (e.keyCode === 27) {
        if (e.target.closest('div[aria-hidden="false"]')) {
          currentMenu.focus();
          toggleSubmenu(currentMenu);
        } else if (e.target.getAttribute("aria-expanded")) {
          toggleSubmenu(currentMenu);
        }
      }
    }

    /**
     * Opens and closes submenus
     * 1. Checks if there is a `button`, this accounts for odd edge case TypeError:
     *    - If focus is on the menu
     *    - But no submenu is expanded
     *    - And esc is keyuped
     * 2. Grabs the submenu that the button's id controls
     * 3. Checks if `aria-expanded` is true
     *    - If so, needs to change "Close" text to Menu Name and close menu
     *        1. Grab text from `h2`
     *        2. Set button text (use `firstChild` here to not clear SVG)
     *        3. Set aria attributes to close menu
     *        4. Set `currentMenu` to false
     *    - If no, needs to change menu text to say "Close" and open menu
     *        1. Set button text (use `firstChild` here to not clear SVG)
     *        2. Set aria attributes to open menu
     *        3. Set `currentMenu` to `button`
     * @param {Object} button - `button.NavigationList-listItem-listLink`
     *
     * Using `firstChild.data` from a StackOverflow thread (https://stackoverflow.com/questions/56140202/how-to-change-text-inside-a-div-without-changing-any-other-element-in-the-div)
     */
    function toggleSubmenu(button) {
      if (button) {
        const submenu = document.getElementById(
          button.getAttribute("aria-controls")
        );
        if (button.getAttribute("aria-expanded") === "true") {
          const { innerText } = submenu.querySelector("h2");
          button.firstChild.data = innerText;
          button.setAttribute("aria-expanded", false);
          submenu.setAttribute("aria-hidden", true);
          currentMenu = false;
        } else {
          button.firstChild.data = "Close";
          button.setAttribute("aria-expanded", true);
          submenu.setAttribute("aria-hidden", false);
          currentMenu = button;
        }
      }
    }
  };
  document.addEventListener("DOMContentLoaded", () => {
    const navigation = document.querySelector("ul.NavigationList");
    const flyoutMenu = new FlyoutMenu(navigation);
    flyoutMenu.init();
  });
})();

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.