CSS Navigations and Menus Components

Horizontal navigation with overflow indicators

This is a simple navigation. When the navigation doesn't have enough space (on mobile for example) the overflow indicators with white gradient (on right and left side) shows up to signalize that the menu is scrollable. In case you don't want those indicators, you can remove them. Don't forget to remove the javascript and CSS for them too. If there is any active item in the menu, it automatically scrolls into the view.
<!--
stylify-variables
	color: '#FFA585'
/stylify-variables
-->
<nav class="
	js-nav
	position:relative overflow:hidden
	[.nav-overflow-indicator]{width:32px;transition:.3s;will-change:transform;height:100%;display:flex;position:absolute;top:0}
">
	<div class="
		s-hidden
		js-nav-overflow-indicator nav-overflow-indicator
		left:0 background:linear-gradient(90deg,#fff,rgba(255,255,255,0))
		[&.s-hidden]{transform:translateX(-50px)}
	"></div>
	<div class="
		js-nav-scrollbar
		display:flex gap:16px overflow:auto
		[a]{text-decoration:none;color:#000;font-weight:bold;white-space:nowrap}
		[a:hover,a.s-selected]{color:$color}
	">
		<a href="#">Link</a>
		<a href="#">Link</a>
		<a href="#">Link</a>
		<a href="#">Link</a>
		<a href="#">Link</a>
		<a href="#">Link</a>
		<a href="#">Link</a>
		<a href="#">Link</a>
		<a href="#">Link</a>
		<a href="#">Link</a>
		<a href="#">Link</a>
		<a href="#">Link</a>
		<a href="#" class="s-selected">Link</a>
		<a href="#">Link</a>
		<a href="#">Link</a>
		<a href="#">Link</a>
		<a href="#">Link</a>
	</div>
	<div class="
		s-hidden
		js-nav-overflow-indicator nav-overflow-indicator
		right:0 background:linear-gradient(-90deg,#fff,rgba(255,255,255,0))
		[&.s-hidden]{transform:translateX(50px)}
	"></div>
</nav>

<script>
	const scrollLinkIntoView = (link) => {
		// TODO this is disabled because it cause scroll on Stylify web
		// Uncomment this so the selected item scrolls into the view
		//link.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'center' });
	};

	const getNavScrollBar = (nav) => nav.querySelector('.js-nav-scrollbar');
	const getNavScrollIndicators = (nav) => {
		const indicators = nav.querySelectorAll('.js-nav-overflow-indicator');

		return {
			left: indicators[0],
			right: indicators[1]
		}
	}

	const indicatorHiddenClass = 's-hidden';
	const scrollIndicatorLimitToHide = 24;
	const toggleNavScrollIndicators = (nav, scrollBar) => {
		scrollBar = scrollBar ?? getNavScrollBar(nav);
		const navWidth = parseInt(window.getComputedStyle(nav).width.match(/\d+/));
		const scrollBarScrollWidth = scrollBar.scrollWidth;
		const indicators = getNavScrollIndicators(nav);

		if (scrollBarScrollWidth <= navWidth) {
			indicators.left.classList.add(indicatorHiddenClass);
			indicators.right.classList.add(indicatorHiddenClass);
			return;
		}

		const leftOverflow = scrollBar.scrollLeft;

		indicators.left.classList.toggle(indicatorHiddenClass, leftOverflow <= scrollIndicatorLimitToHide);

		indicators.right.classList.toggle(
			indicatorHiddenClass,
			scrollBarScrollWidth - leftOverflow <= navWidth + scrollIndicatorLimitToHide
		);
	}

	const navigations = document.querySelectorAll('.js-nav');

	navigations.forEach((nav) => {
		const scrollBar = getNavScrollBar(nav);
		const indicators = getNavScrollIndicators(nav);

		nav.querySelectorAll('a').forEach((link) => link.addEventListener('click', () => scrollLinkIntoView(link)));

		for (const indicator of Object.values(indicators)) {
			indicator.addEventListener('touchstart', () => indicator.classList.add(indicatorHiddenClass));
			indicator.addEventListener('mouseover', () => indicator.classList.add(indicatorHiddenClass));
			indicator.addEventListener('mouseout', () => toggleNavScrollIndicators(nav, scrollBar));
		}

		scrollBar.addEventListener('scroll', () => toggleNavScrollIndicators(nav, scrollBar));

		toggleNavScrollIndicators(nav);
	});

	window.addEventListener('load', () => {
		document.querySelectorAll('.s-selected').forEach((link) => scrollLinkIntoView(link));
	});

	window.addEventListener('resize', () => navigations.forEach((nav) => toggleNavScrollIndicators(nav)));
</script>
<!--
stylify-variables
	color: '#FFA585'
/stylify-variables

stylify-components
	navigation: `
		position:relative overflow:hidden

		.nav-overflow-indicator {
			width:32px transition:.3s will-change:transform height:100% display:flex position:absolute top:0
		}

		.nav-overflow-indicator--left {
			left:0 background:linear-gradient(90deg,#fff,rgba(255,255,255,0))
			&.s-hidden {transform:translateX(-50px)}
		}

		.nav-overflow-indicator--right {
			right:0 background:linear-gradient(-90deg,#fff,rgba(255,255,255,0))
			&.s-hidden { transform:translateX(50px) }
		}

		.nav-scrollbar {
			display:flex gap:16px overflow:auto
			a {
				text-decoration:none color:#000 font-weight:bold white-space:nowrap
				&:hover, &.s-selected { color:$color }
			}
		}
	`
/stylify-components
-->
<nav class="js-nav navigation position:relative overflow:hidden">
	<div class="s-hidden js-nav-overflow-indicator nav-overflow-indicator nav-overflow-indicator--left"></div>
	<div class="js-nav-scrollbar nav-scrollbar">
		<a href="#">Link</a>
		<a href="#">Link</a>
		<a href="#">Link</a>
		<a href="#">Link</a>
		<a href="#">Link</a>
		<a href="#">Link</a>
		<a href="#">Link</a>
		<a href="#">Link</a>
		<a href="#">Link</a>
		<a href="#">Link</a>
		<a href="#">Link</a>
		<a href="#">Link</a>
		<a href="#" class="s-selected">Link</a>
		<a href="#">Link</a>
		<a href="#">Link</a>
		<a href="#">Link</a>
		<a href="#">Link</a>
	</div>
	<div class="s-hidden js-nav-overflow-indicator nav-overflow-indicator nav-overflow-indicator--right"></div>
</nav>

<script>
	const scrollLinkIntoView = (link) => {
		link.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'center' });
	};

	const getNavScrollBar = (nav) => nav.querySelector('.js-nav-scrollbar');
	const getNavScrollIndicators = (nav) => {
		const indicators = nav.querySelectorAll('.js-nav-overflow-indicator');

		return {
			left: indicators[0],
			right: indicators[1]
		}
	}

	const indicatorHiddenClass = 's-hidden';
	const scrollIndicatorLimitToHide = 24;
	const toggleNavScrollIndicators = (nav, scrollBar) => {
		scrollBar = scrollBar ?? getNavScrollBar(nav);
		const navWidth = parseInt(window.getComputedStyle(nav).width.match(/\d+/));
		const scrollBarScrollWidth = scrollBar.scrollWidth;
		const indicators = getNavScrollIndicators(nav);

		if (scrollBarScrollWidth <= navWidth) {
			indicators.left.classList.add(indicatorHiddenClass);
			indicators.right.classList.add(indicatorHiddenClass);
			return;
		}

		const leftOverflow = scrollBar.scrollLeft;

		indicators.left.classList.toggle(indicatorHiddenClass, leftOverflow <= scrollIndicatorLimitToHide);

		indicators.right.classList.toggle(
			indicatorHiddenClass,
			scrollBarScrollWidth - leftOverflow <= navWidth + scrollIndicatorLimitToHide
		);
	}

	const navigations = document.querySelectorAll('.js-nav');

	navigations.forEach((nav) => {
		const scrollBar = getNavScrollBar(nav);
		const indicators = getNavScrollIndicators(nav);

		nav.querySelectorAll('a').forEach((link) => link.addEventListener('click', () => scrollLinkIntoView(link)));

		for (const indicator of Object.values(indicators)) {
			indicator.addEventListener('touchstart', () => indicator.classList.add(indicatorHiddenClass));
			indicator.addEventListener('mouseover', () => indicator.classList.add(indicatorHiddenClass));
			indicator.addEventListener('mouseout', () => toggleNavScrollIndicators(nav, scrollBar));
		}

		scrollBar.addEventListener('scroll', () => toggleNavScrollIndicators(nav, scrollBar));

		toggleNavScrollIndicators(nav);
	});

	window.addEventListener('load', () => {
		document.querySelectorAll('.s-selected').forEach((link) => scrollLinkIntoView(link));
	});

	window.addEventListener('resize', () => navigations.forEach((nav) => toggleNavScrollIndicators(nav)));
</script>
Simple mobile menu that opens after clicking on a hamburger icon.
<!--
stylify-variables
	color: '#C81D77'
/stylify-variables
-->

Click to open the navigation
<a role="button" class="
	js-mobile-nav-toggle
	display:inline-flex align-items:center justify-content:center width:42px height:42px border-radius:50px cursor:pointer background:$color transition:background_.3s
	hover:background:lighten($color,20)
	focus:background:lighten($color,20)
">
	<svg viewBox="0 0 100 80" fill="#fff" width="16" height="16">
		<rect width="100" height="20" rx="10"></rect>
		<rect y="30" width="100" height="20" rx="10"></rect>
		<rect y="60" width="100" height="20" rx="10"></rect>
	</svg>
</a>

<aside class="
	js-mobile-nav s-hidden
	will-change:transform
	transition:transform_.3s display:flex align-items:flex-end position:fixed top:0 right:0 flex-direction:column width:100vw height:100vh z-index:2
	[&.s-hidden]{transform:translateX(100%)}
">
	<div class="js-mobile-nav-obfuscator position:absolute inset:0 background:rgba(0,0,0,.5)"></div>
	<nav role="navigation" class="
		display:flex flex-direction:column position:relative
		background:#fff height:100vh width:70% max-width:300px
	">
		<div class="position:absolute width:100% border-bottom:1px_solid_#eee z-index:2 top:0 left:0 padding:12px display:flex justify-content:space-between align-items:center background:rgba(255,255,255,0.8) backdrop-filter:blur(3px)">
			<a href="#" aria-label="Home Button" class="display:flex">
				<img src="https://stylifycss.com/images/logo/horizontal.svg" loading="lazy" fetchpriority="low" width="100" height="28">
			</a>
			<a role="button" aria-label="Close Menu" class="cursor:pointer align-self:flex-end js-mobile-nav-toggle">
				<svg width="40" height="40">
					<path d="m10 10 20 20m0-20L10 30" stroke="#000" stroke-width="4"/>
				</svg>
			</a>
		</div>
		<div class="
			gap:5% display:flex justify-content:flex-end
			[a]{word-break:break-word;font-weight:bold;white-space:normal;text-decoration:none;color:#000;padding:8px_0;hover:color:$color}
			[a:not(:last-of-type)]{border-bottom:1px_solid_#eee}
			[ul]{list-style:none}
			[li::before]{color:orange}
			flex-direction:column gap:12px height:100vh padding:84px_12px_12px_12px overflow:auto justify-content:flex-start
		">
			<a href="#" class="link">Menu link</a>
			<a href="#" class="link">Menu link</a>
			<a href="#" class="link">Menu link</a>
			<a href="#" class="link">Menu link</a>
			<a href="#" class="link">Menu link</a>
			<a href="#" class="link">Menu link</a>
			<a href="#" class="link">Menu link</a>
			<a href="#" class="link">Menu link</a>
			<a href="#" class="link">Menu link</a>
			<a href="#" class="link">Menu link</a>
			<a href="#" class="link">Menu link</a>
			<a href="#" class="link">Menu link</a>
		</div>
	</nav>
</aside>

<script>
	const mobileNavHiddenClass = 's-hidden';
	const mobileNavClassPrefix = 'js-mobile-nav';

	const toggleMobileNavigationVisibility = () => {
		const headerNavigation = document.querySelector(`.${mobileNavClassPrefix}`);

		if (headerNavigation?.classList.contains(mobileNavHiddenClass)) {
			headerNavigation.classList.remove(mobileNavHiddenClass);
		} else {
			headerNavigation?.classList.add(mobileNavHiddenClass);
		}
	}

	document.querySelector(`.${mobileNavClassPrefix}-obfuscator`).addEventListener('click', () => toggleMobileNavigationVisibility());

	document.querySelectorAll(`.${mobileNavClassPrefix}-toggle`).forEach((button) => {
		button.addEventListener('click', () => toggleMobileNavigationVisibility());
	});

</script>
<!--
stylify-variables
	color: '#C81D77'
/stylify-variables

stylify-components
	'mobile-navigation': `
		will-change:transform
		transition:transform_.3s display:flex align-items:flex-end position:fixed top:0 right:0 flex-direction:column width:100vw height:100vh z-index:2
		&.s-hidden { transform:translateX(100%) }
	`,
	'mobile-navigation__header': `
		position:absolute width:100% border-bottom:1px_solid_#eee z-index:2 top:0 left:0 padding:12px display:flex justify-content:space-between align-items:center background:rgba(255,255,255,0.8) backdrop-filter:blur(3px)
	`,
	'mobile-navigation__header-close': 'cursor:pointer align-self:flex-end',
	'mobile-navigation__links': `
		gap:5% display:flex justify-content:flex-end flex-direction:column gap:12px height:100vh padding:84px_12px_12px_12px overflow:auto justify-content:flex-start
		a { word-break:break-word font-weight:bold white-space:normal text-decoration:none color:#000 padding:8px_0 hover:color:$color }
		a:not(:last-of-type) { border-bottom:1px_solid_#eee }
		ul { list-style:none }
		li::before { color:orange }
	`,
	'mobile-navigation-hamburger': `
		display:inline-flex align-items:center justify-content:center width:42px height:42px border-radius:50px cursor:pointer background:$color transition:background_.3s
		hover:background:lighten($color,20)
		focus:background:lighten($color,20)
	`
/stylify-components
-->

Click to open the navigation
<a role="button" class="js-mobile-nav-toggle mobile-navigation-hamburger">
	<svg viewBox="0 0 100 80" fill="#fff" width="16" height="16">
		<rect width="100" height="20" rx="10"></rect>
		<rect y="30" width="100" height="20" rx="10"></rect>
		<rect y="60" width="100" height="20" rx="10"></rect>
	</svg>
</a>

<aside class="js-mobile-nav s-hidden mobile-navigation">
	<div class="js-mobile-nav-obfuscator position:absolute inset:0 background:rgba(0,0,0,.5)"></div>
	<nav role="navigation" class="
		display:flex flex-direction:column position:relative
		background:#fff height:100vh width:70% max-width:300px
	">
		<div class="mobile-navigation__header">
			<a href="#" aria-label="Home Button" class="display:flex">
				<img src="https://stylifycss.com/images/logo/horizontal.svg" loading="lazy" fetchpriority="low" width="100" height="28">
			</a>
			<a role="button" aria-label="Close Menu" class="mobile-navigation__header-close js-mobile-nav-toggle">
				<svg width="40" height="40">
					<path d="m10 10 20 20m0-20L10 30" stroke="#000" stroke-width="4"/>
				</svg>
			</a>
		</div>
		<div class="mobile-navigation__links">
			<a href="#" class="link">Menu link</a>
			<a href="#" class="link">Menu link</a>
			<a href="#" class="link">Menu link</a>
			<a href="#" class="link">Menu link</a>
			<a href="#" class="link">Menu link</a>
			<a href="#" class="link">Menu link</a>
			<a href="#" class="link">Menu link</a>
			<a href="#" class="link">Menu link</a>
			<a href="#" class="link">Menu link</a>
			<a href="#" class="link">Menu link</a>
			<a href="#" class="link">Menu link</a>
			<a href="#" class="link">Menu link</a>
		</div>
	</nav>
</aside>

<script>
	const mobileNavHiddenClass = 's-hidden';
	const mobileNavClassPrefix = 'js-mobile-nav';

	const toggleMobileNavigationVisibility = () => {
		const headerNavigation = document.querySelector(`.${mobileNavClassPrefix}`);

		if (headerNavigation?.classList.contains(mobileNavHiddenClass)) {
			headerNavigation.classList.remove(mobileNavHiddenClass);
		} else {
			headerNavigation?.classList.add(mobileNavHiddenClass);
		}
	}

	document.querySelector(`.${mobileNavClassPrefix}-obfuscator`).addEventListener('click', () => toggleMobileNavigationVisibility());

	document.querySelectorAll(`.${mobileNavClassPrefix}-toggle`).forEach((button) => {
		button.addEventListener('click', () => toggleMobileNavigationVisibility());
	});

</script>