<!-- 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.