The Olivero theme has a responsive menu, let's see how it works.
Here is what it looks like, with a few dummy links:

As the window size shrinks, it switches to the mobile menu when it runs out of space.

Olivero has a few js files related to navigation.
/**
* @file
* Controls the visibility of desktop navigation.
*
* Shows and hides the desktop navigation based on scroll position and controls
* the functionality of the button that shows/hides the navigation.
*/
Included in olivero/navigation-base library, required by olivero/navigation-primary.
Create global object to track the state.
Drupal.olivero = {}
This adds the isDesktopNav() function, and makes it available globally as Drupal.olivero.isDesktopNav().
/**
* Checks if the mobile navigation button is visible.
*
* @return {boolean}
* True if navButtons is hidden, false if not.
*/
function isDesktopNav() {
const navButtons = document.querySelector(
'[data-drupal-selector="mobile-buttons"]',
);
return navButtons
? window.getComputedStyle(navButtons).getPropertyValue('display') ===
'none'
: false;
}
Drupal.olivero.isDesktopNav = isDesktopNav;
These two variables are set up for other functions to use.
const stickyHeaderToggleButton = document.querySelector(
'[data-drupal-selector="sticky-header-toggle"]',
);
const siteHeaderFixable = document.querySelector(
'[data-drupal-selector="site-header-fixable"]',
);
This one does what it says.
function stickyHeaderIsEnabled() {
return stickyHeaderToggleButton.getAttribute('aria-checked') === 'true';
}
Uses localStorage to remember expanded state.
/**
* Save the current sticky header expanded state to localStorage, and set
* it to expire after two weeks.
*
* @param {boolean} expandedState
* Current state of the sticky header button.
*/
function setStickyHeaderStorage(expandedState) {}
Just updates the localStorage expiration.
/**
* Update the expiration date if the sticky header expanded state is set.
*
* @param {boolean} expandedState
* Current state of the sticky header button.
*/
function updateStickyHeaderStorage(expandedState) {}
/**
* Toggle the state of the sticky header between always pinned and
* only pinned when scrolled to the top of the viewport.
*
* @param {boolean} pinnedState
* State to change the sticky header to.
*/
function toggleStickyHeaderState(pinnedState) {
if (isDesktopNav()) {
siteHeaderFixable.classList.toggle('is-expanded', pinnedState);
stickyHeaderToggleButton.setAttribute('aria-checked', pinnedState);
}
}
/**
* Return the sticky header's stored state from localStorage.
*
* @return {boolean}
* Stored state of the sticky header.
*/
function getStickyHeaderStorage() {}
toggleDesktopNavVisibility()
IntersectionObserver callback.
// Only enable scroll interactivity if the browser supports Intersection
// Observer.
// @see https://github.com/w3c/IntersectionObserver/blob/master/polyfill/intersection-observer.js#L19-L21
if (
'IntersectionObserver' in window &&
'IntersectionObserverEntry' in window &&
'intersectionRatio' in window.IntersectionObserverEntry.prototype
) {
const fixableElements = document.querySelectorAll(
'[data-drupal-selector="site-header-fixable"], [data-drupal-selector="social-bar-inner"]',
);
function toggleDesktopNavVisibility(entries) {}
/**
* Gets the root margin by checking for various toolbar classes.
*
* @return {string}
* Root margin for the Intersection Observer options object.
*/
function getRootMargin() {}
Set up IntersectionObserver.
/**
* Monitor the navigation position.
*/
function monitorNavPosition() {}
Set up event subscribers.
if (stickyHeaderToggleButton) {
stickyHeaderToggleButton.addEventListener('click', () => {
const pinnedState = !stickyHeaderIsEnabled();
toggleStickyHeaderState(pinnedState);
setStickyHeaderStorage(pinnedState);
});
}
Misc setup.
// If header is pinned open and a header element gains focus, scroll to the
// top of the page to ensure that the header elements can be seen.
const siteHeaderInner = document.querySelector(
'[data-drupal-selector="site-header-inner"]',
);
if (siteHeaderInner) {
siteHeaderInner.addEventListener('focusin', () => {
if (isDesktopNav() && !stickyHeaderIsEnabled()) {
const header = document.querySelector(
'[data-drupal-selector="site-header"]',
);
const headerNav = header.querySelector(
'[data-drupal-selector="header-nav"]',
);
const headerMargin = header.clientHeight - headerNav.clientHeight;
if (window.scrollY > headerMargin) {
window.scrollTo(0, headerMargin);
}
}
});
}
Initialize functions.
monitorNavPosition();
updateStickyHeaderStorage(getStickyHeaderStorage());
toggleStickyHeaderState(getStickyHeaderStorage());
The file description explains how it works.
/**
* @file
* This script watches the desktop version of the primary navigation. If it
* wraps to two lines, it will automatically transition to a mobile navigation
* and remember where it wrapped so it can transition back.
*/
The init function sets up a resizeObserver to call checkIfDesktopNavigationWraps() when the primary nav resizes.
/**
* Set up Resize Observer to listen for changes to the size of the primary
* navigation.
*
* @param {Element} primaryNav - The primary navigation's top-level <ul> element.
*/
function init(primaryNav) {
const resizeObserver = new ResizeObserver(checkIfDesktopNavigationWraps);
resizeObserver.observe(primaryNav);
}
/**
* Callback from Resize Observer. This checks if the primary navigation is
* wrapping, and if so, transitions to the mobile navigation.
*
* @param {ResizeObserverEntry} entries - Object passed from ResizeObserver.
*/
function checkIfDesktopNavigationWraps(entries)
Issues
Olivero: Support second-level navigation submenus on secondary menu
Olivero Doesn't Explain Menu Behavior When Migrating From Other Themes
Unfold the main menu on mobile and improve navigation
Olivero: Header menu should not close if menu item has focus
Provide Olivero theme settings to control the behaviour of the header regions
Only close Olivero sub-menus when resize results in a different menu format
Olivero: Add option for reduced header/branding height
Olivero: Fix long menu (desktop) design
Olivero: Focus outline does not accommodate on long text in primary & secondary navigation
Keyboard Navigation: Arrow Keys in menu for Olivero
Add Olivero-like primary navigation to Starterkit theme
Olivero: Z-index issue with table with sticky header
Olivero's focus state outline can get cut off certain situations
How to keep the menu open when it goes to mobile version ?
Let users disable animation in Olivero
Under certain circumstances the primary navigation can create horizontal scrollbar
Olivero should use Drupal.displace() to place the mobile menu
Get rid of jQuery in displace event
Opportunity to refactor parts of Olivero's second-level-navigation JS
When I'm logged in, the secondary menu does not want to open
Navigation top bar overlaps with Olivero menu
Olivero: Mobile tabs can become out of order if browser is resized
Add a gradient effect when the main menu is not fully displayed
Potential header menu "X" close-icon usability issue in Olivero