<!-- for use with <use> -->
<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="#000" stroke-width="2" fill="transparent" stroke-linecap="round" />
  </symbol>
</svg>

<!-- In the real world, all hrefs would have go to real, unique URLs, not a "#" -->
<nav id="site-navigation" class="site-navigation" aria-label="Clickable Menu Demonstration">
  <ul class="main-menu clicky-menu no-js">
    <li>
      <a href="#">Home</a>
    </li>
    <li>
      <a href="#">
        Services
        <svg aria-hidden="true" width="16" height="16">
          <use xlink:href="#arrow" />
        </svg>
      </a>
      <ul>
        <li><a href="#">Design</a></li>
        <li><a href="#">Development</a></li>
        <li><a href="#">Accessibility</a></li>
        <li><a href="#">Content Strategy</a></li>
        <li><a href="#">Training</a></li>
      </ul>
    </li>
    <li>
      <a href="#">
        Portfolio
        <svg aria-hidden="true" width="16" height="16">
          <use xlink:href="#arrow" />
        </svg>
      </a>
      <ul>
        <li><a href="#">Nonprofits</a></li>
        <li><a href="#">Higher Education</a></li>
        <li><a href="#">Associations</a></li>
        <li><a href="#">Consultants</a></li>
      </ul>
    </li>
    <li>
      <a href="#">
        About
        <svg aria-hidden="true" width="16" height="16">
          <use xlink:href="#arrow" />
        </svg>
      </a>
      <ul>
        <li><a href="#">Mission</a></li>
        <li><a href="#">History</a></li>
        <li><a href="#">Contact</a></li>
      </ul>
    </li>
  </ul>
</nav>

<p class="github">Now on <a href="https://github.com/mrwweb/clicky-menus">Github</a> and <a href="https://www.npmjs.com/package/clicky-menus">NPM!</a></p>
/**
 * Clicky Menus v1.2.0
 */

/**
 * Initial state, hidden off screen
 */
.clicky-menu ul {
  position: absolute;
  top: 100%;
  left: 0;
  visibility: hidden; /*[1]*/
}

.clicky-menu > li {
  position: relative;
}

/**
 * No JS fallback
 *
 * Triggers menus on hover rather than click. Supports keyboard navigation in modern browsers.
 */
.clicky-menu.no-js li:hover > ul {
  visibility: visible;
}
.clicky-menu.no-js li:focus-within > ul { /*[2]*/
  visibility: visible;
}

/**
 * Open/Close Menu Behavior with JS
 */
.clicky-menu ul[aria-hidden="false"] {
  visibility: visible;
}

/* Prevent offscreen-submenus */
.clicky-menu .sub-menu--right {
  left: auto !important;
  right: 0 !important;
}

/**
 * Footnotes
 *
 * [1] Using `visibility` instead of `display` allows for easier transitions and animation of submenus
 * [2] Must be a separate ruleset so that hover works in non-modern browsers
 */


/* DEMO CSS */
body {
  min-height: 100vh;
  background: linear-gradient(-37deg, teal, purple, orange) center/cover no-repeat;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI",
         Roboto, Oxygen-Sans, Ubuntu, Cantarell,
         "Helvetica Neue", sans-serif;
} 

/* Hidden SVG used for down arrows */
svg[hidden] {
  display: none;
  position: absolute;
}

.site-navigation {
  width: 86%;
  max-width: 782px;
  margin: 100px auto;
  box-shadow: 2px 2px 4px rgba(0,0,0,.2);
  background-color: #eee;
  border-radius: 4px;
}

.clicky-menu {
  justify-content: stretch;
  margin: 0;
  padding: 0;
  list-style: none;
}
@media (min-width: 540px) {
  .clicky-menu {
    display: flex;
  }
}

/* General Link & Button Styles */
.clicky-menu a,
.clicky-menu button {
  margin: .25em;
  padding: 1em;
  background: transparent;
  color: #000;
  font-family: inherit;
  text-decoration: none;
  border-radius: 3px;
}

.clicky-menu a:hover,
.clicky-menu button:hover {
  background: #fff;
}

.clicky-menu a:focus,
.clicky-menu button:focus {
  outline: .125em dotted purple;
  outline-offset: -.125em;
}

/* Top Level Items */
.clicky-menu > li {
  flex: 1 1 auto;
  display: flex;
  justify-content: stretch;
  flex-wrap: wrap;
}

.clicky-menu > li > a,
.clicky-menu > li > button {
  flex: 1 0 auto;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  border: 0;
  font-size: inherit;
  font-weight: 600;
  line-height: 1.5;
  cursor: pointer;
}
@media (min-width: 540px) {
  .clicky-menu > li > a,
  .clicky-menu > li > button {
    justify-content: center;
  } 
}

/* Icon */
.clicky-menu svg {
  width: 1em;
  height: 1em;
  margin-left: .5em;
}

.clicky-menu [aria-expanded="true"] svg {
  transform: scaleY(-1);
}

/* Submenu Styles */
.clicky-menu ul {
  min-width: 100%;
  margin-top: .25em;
  padding: 0;
  list-style: none;
  background-color: #eee;
  border-radius: 3px;
}

@media (min-width: 540px) {
  .clicky-menu ul {
    box-shadow: 2px 4px 4px rgba(0,0,0,.2);
    transform: translateY(1em);
    opacity: .5;
    transition-property: transform opacity;
    transition-duration: .1s;
  }
}

/* Ensure no-js support works by covering the .25em margin-top gap between submenu and parent item with a pseudo-element that extends the "surface" of the submenu. If you don't care as much about no-js mode, you could also just set margin-top: 0 when .no-js is present.  */
.clicky-menu.no-js ul::before {
  position: absolute;
  display: block;
  content: "";
  width: 100%;
  height: .25em;
  top: -.25em;
}

/* Responsive Submenu Behavior */
.clicky-menu ul[aria-hidden="false"] {
  position: static;
  width: 100%;
  flex: 0 0 auto;
}

@media (min-width: 540px) {
  .clicky-menu ul[aria-hidden="false"] {
    position: absolute;
    width: auto;
    transform: translateY(0);
    opacity: 1;
  }
}

/* Submenu Links */
.clicky-menu ul a {
  display: block;
  padding-top: .375em;
  padding-bottom: .375em;
}
@media (min-width: 540px) {
  .clicky-menu ul a {
    padding: .375em 1em;
    white-space: nowrap;
  }
}

.github {
  position: fixed;
  width: 90%;
  left: 5%;
  bottom: 10px;
  text-align: center;
  color: #fff;
}
.github a {
  color: inherit;
}
View Compiled
/**
 * Clicky Menus v1.2.0
 */

( function() {
  'use strict';

  const ClickyMenus = function( menu ) {
    // DOM element(s)
    const container = menu.parentElement;
    let currentMenuItem,
      i,
      len;

    this.init = function() {
      menuSetup();
      document.addEventListener( 'click', closeIfClickOutsideMenu );
      // custom event to allow outside scripts to close submenus
      menu.addEventListener( 'clickyMenusClose', closeOpenSubmenu );
    };

    /*===================================================
    =            Menu Open / Close Functions            =
    ===================================================*/
    function toggleOnMenuClick( e ) {
      const button = e.currentTarget;

      // close open menu if there is one
      if ( currentMenuItem && button !== currentMenuItem ) {
        toggleSubmenu( currentMenuItem );
      }

      toggleSubmenu( button );
    }

    function toggleSubmenu( button ) {
      const submenu = document.getElementById( button.getAttribute( 'aria-controls' ) );

      if ( 'true' === button.getAttribute( 'aria-expanded' ) ) {
        button.setAttribute( 'aria-expanded', false );
        submenu.setAttribute( 'aria-hidden', true );
        currentMenuItem = false;
      } else {
        button.setAttribute( 'aria-expanded', true );
        submenu.setAttribute( 'aria-hidden', false );
        preventOffScreenSubmenu( submenu );
        currentMenuItem = button;
      }
    }

    function preventOffScreenSubmenu( submenu ) {
      const   screenWidth = window.innerWidth ||
                  document.documentElement.clientWidth ||
                  document.body.clientWidth,
        parent = submenu.offsetParent,
        menuLeftEdge = parent.getBoundingClientRect().left,
        menuRightEdge = menuLeftEdge + submenu.offsetWidth;

      if ( menuRightEdge + 32 > screenWidth ) { // adding 32 so it's not too close
        submenu.classList.add( 'sub-menu--right' );
      }
    }

    function closeOnEscKey( e ) {
      if (  27 === e.keyCode ) {
        // we're in a submenu item
        if ( null !== e.target.closest( 'ul[aria-hidden="false"]' ) ) {
          currentMenuItem.focus();
          toggleSubmenu( currentMenuItem );

        // we're on a parent item
        } else if ( 'true' === e.target.getAttribute( 'aria-expanded' ) ) {
          toggleSubmenu( currentMenuItem );
        }
      }
    }

    function closeIfClickOutsideMenu( e ) {
      if ( currentMenuItem && ! e.target.closest( '#' + container.id ) ) {
        toggleSubmenu( currentMenuItem );
      }
    }

    function closeOpenSubmenu() {
      if( currentMenuItem ) {
        toggleSubmenu( currentMenuItem );
      }
    }

    /*===========================================================
    =            Modify Menu Markup & Bind Listeners            =
    =============================================================*/
    function menuSetup() {
      menu.classList.remove( 'no-js' );
      const submenuSelector = 'clickySubmenuSelector' in menu.dataset ? menu.dataset.clickySubmenuSelector : 'ul';

      menu.querySelectorAll( submenuSelector ).forEach( ( submenu ) => {
        const menuItem = submenu.parentElement;

        if ( 'undefined' !== typeof submenu ) {
          const button = convertLinkToButton( menuItem );

          setUpAria( submenu, button );

          // bind event listener to button
          button.addEventListener( 'click', toggleOnMenuClick );
          menu.addEventListener( 'keyup', closeOnEscKey );
        }
      } );
    }

    /**
     * Why do this? See https://justmarkup.com/articles/2019-01-21-the-link-to-button-enhancement/
     *
     * @param {HTMLElement} menuItem An element representing a link to be converted to a button
     */
    function convertLinkToButton( menuItem ) {
      const   link = menuItem.getElementsByTagName( 'a' )[ 0 ],
        linkHTML = link.innerHTML,
        linkAtts = link.attributes,
        button = document.createElement( 'button' );

      if ( null !== link ) {
        // copy button attributes and content from link
        button.innerHTML = linkHTML.trim();
        for ( i = 0, len = linkAtts.length; i < len; i++ ) {
          const attr = linkAtts[ i ];
          if ( 'href' !== attr.name ) {
            button.setAttribute( attr.name, attr.value );
          }
        }

        menuItem.replaceChild( button, link );
      }

      return button;
    }

    function setUpAria( submenu, button ) {
      const submenuId = submenu.getAttribute( 'id' );

      let id;
      if ( null === submenuId ) {
        id = button.textContent.trim().replace( /\s+/g, '-' ).toLowerCase() + '-submenu';
      } else {
        id = submenuId + '-submenu';
      }

      // set button ARIA
      button.setAttribute( 'aria-controls', id );
      button.setAttribute( 'aria-expanded', false );

      // set submenu ARIA
      submenu.setAttribute( 'id', id );
      submenu.setAttribute( 'aria-hidden', true );
    }
  };

  /* Create a ClickMenus object and initiate menu for any menu with .clicky-menu class */
  document.addEventListener( 'DOMContentLoaded', function() {
    const menus = document.querySelectorAll( '.clicky-menu' );

    menus.forEach( ( menu ) => {
      const clickyMenu = new ClickyMenus( menu );
      clickyMenu.init();
    } );
  } );
}() );

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.