Header
About
What does it do?
- The primary way of navigating the website
- Standardised pattern
When to use it?
- Every page must have a header and a footer
Header
Skip link
There is a hidden skip link for users with screen reader assistive technology. This allows users to quickly access the main content. All pages must include this.
Ensure there is a HTML element on the page with the ID of wmnds-main-content
so the user can skip to this section.
Logo
This must always be the Transport for West Midlands logo.
Phase indicator
This component must be used if the service is not live, using the phase indicator guidance.
Navigation
The three top-level navigation items were decided by identifying our user’s top tasks using Gerry McGovern’s methodology. We overlaid a heatmap of the home page, where we found the same three areas were the most-used areas.
The top-level items open a structured megamenu to avoid cognitive overload. Users are familiar with this pattern from online shopping and transport operators.
We only use three top-level items as we prefer not to use hamburger menus on mobile. Users are required to make an extra click to reveal the navigation, therefore you cannot glance at navigation options.
Mobile apps
When designing an app, you should use a tab bar and avoid using hamburger items wherever possible. Limit the tabs to the most important tasks using user research.
Search
This function expands the mega menu and reveals the search bar along with the ‘I want to…’ feature.
The ‘I want to...’ feature shows a list of the top page links which users access through search. This is to help users find the information they are searching for faster. These links are regularly updated using search data to maintain their relevance.
On mobile, the mega menu search function uses a primary purple background instead of the standard white background. Our user testing found users preferred the white link text on the primary purple background as it is clearer to read.
HTML markup
<!-- Skip to content link -->
<a href="#wmnds-main-content" title="Skip to main content" target="_self" class="wmnds-link wmnds-header__skip-link">
Skip to main content
</a>
<!-- Main header section -->
<header>
<div class="wmnds-bg-white wmnds-p-t-md wmnds-p-b-md wmnds-cookies-banner">
<div class="wmnds-container">
<div class="wmnds-col-1 wmnds-col-md-3-4 wmnds-col-lg-2-3">
<h3>Your privacy settings</h3>
<p>
We use cookies to help you with journey planning and relevant disruptions, remember your login and show you
content you might be interested in. If you’re happy with the use of cookies by West Midlands Combined Authority
and our selected partners, click ‘Accept all cookies’. Or click ‘Manage cookies’ to learn more.
</p>
<div class="wmnds-grid wmnds-grid--justify-between wmnds-cookies-banner__group-buttons">
<button class="wmnds-btn wmnds-col-1 wmnds-col-sm-1 wmnds-col-md-12-24 wmnds-cookies-banner__accept-all-cookies wmnds-text-align-center" type="button">
Accept all cookies
</button>
<a href="https://www.tfwm.org.uk/manage-cookies/" title="link title" target="_self" class="wmnds-btn wmnds-btn wmnds-col-1 wmnds-col-sm-1 wmnds-col-md-12-24 wmnds-text-align-center">
Manage Cookies
</a>
</div>
</div>
</div>
</div>
<div class="wmnds-header wmnds-header--mega-menu">
<div class="wmnds-container wmnds-grid wmnds-grid--align-center wmnds-grid--justify-between">
<div class="wmnds-header__vertical-align wmnds-col-auto">
<!-- Logo -->
<a class="wmnds-header__logo-link" href="/" title="Transport for West Midlands Design System">
<img class="wmnds-header__logo" alt="Transport for West Midlands logo" src="/img/logo.svg" />
</a>
</div>
<!-- Mega menu nav items-->
<nav id="mega-menu-example-full" class="wmnds-mega-menu">
<!-- Mobile toggle button -->
<button class="wmnds-mega-menu__mobile-toggle wmnds-btn wmnds-btn--secondary wmnds-hide-desktop" aria-expanded="false" aria-controls="mega-menu-example-full-primary-menu">Menu
<svg class="wmnds-mega-menu__close-icon">
<use xlink:href="#wmnds-general-cross" href="#wmnds-general-cross"></use>
</svg>
</button>
<!-- Container for mega menu - allows scrolling on mobile nav -->
<div id="mega-menu-example-full-primary-menu" class="wmnds-mega-menu__scroll-controller">
<!-- Start primary (level 1) navigation -->
<ul class="wmnds-mega-menu__primary-menu">
<li class="wmnds-mega-menu__primary-menu-item">
<!-- Show swift logo in nav if is swift & tickets link -->
<!-- allow primary (level 1) item to link if specified -->
<button target="_self" class="wmnds-mega-menu__primary-menu-link">
Styles
</button>
</li>
<li class="wmnds-mega-menu__primary-menu-item">
<!-- Show swift logo in nav if is swift & tickets link -->
<!-- allow primary (level 1) item to link if specified -->
<button target="_self" class="wmnds-mega-menu__primary-menu-link">
Components
</button>
</li>
<li class="wmnds-mega-menu__primary-menu-item">
<!-- Show swift logo in nav if is swift & tickets link -->
<!-- allow primary (level 1) item to link if specified -->
<button target="_self" class="wmnds-mega-menu__primary-menu-link">
Patterns
</button>
</li>
<li class="wmnds-mega-menu__primary-menu-item">
<!-- Show swift logo in nav if is swift & tickets link -->
<!-- allow primary (level 1) item to link if specified -->
<button target="_self" class="wmnds-mega-menu__primary-menu-link">
User research
</button>
</li>
</ul>
<!-- End primary (level 1) navigation -->
</div>
<!-- End scrollable container -->
</nav>
</div>
</div>
<div class="wmnds-container">
<!-- Phase banner -->
<div class="wmnds-banner-container">
<div class="wmnds-col-1">
<div class="wmnds-banner-container__phase-wrapper">
<span class="wmnds-phase-indicator">
Beta
</span>
</div>
<p class="wmnds-banner-container__text">
This is a new service - your
<a href="https://github.com/wmcadigital/wmn-design-system/issues" title="WMN Design System Github" target="_blank" class="wmnds-link" rel="noopener noreferrer">
feedback
</a>
will help us to improve it.
</p>
</div>
</div>
<!-- Breadcrumbs -->
<nav aria-label="Breadcrumbs" class="wmnds-breadcrumb">
<ol class="wmnds-breadcrumb__list">
<li class="wmnds-breadcrumb__list-item">
<a href="/" class="wmnds-breadcrumb__link">
Home
</a>
</li>
<li class="wmnds-breadcrumb__list-item">
<a href="/patterns" class="wmnds-breadcrumb__link">
Patterns
</a>
</li>
<li class="wmnds-breadcrumb__list-item">
<a href="/patterns/header" class="wmnds-breadcrumb__link wmnds-breadcrumb__link--current" aria-current="page">
Header
</a>
</li>
</ol>
</nav>
</div>
</header>
Recommended javascript (browser compatible)
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports["default"] = void 0;
var headerJs = function headerJs() {
// get mega menu elements
var megaMenus = document.querySelectorAll('.wmnds-mega-menu');
var mobileMenu = window.matchMedia('(max-width: 767px)');
/*
Mega menu helper functions
*/
// getMenuLink returns a specified menu link from a specified array
// currentIndex = index of the link that is currently focused
// array = array to move through
// direction = next, prev,
var getMenuLink = function getMenuLink(currentIndex, array, direction) {
var menuLink = null;
if (array) {
if (direction === 'prev') {
// return previous link in specified array if there is one else return null;
menuLink = array[currentIndex - 1] ? array[currentIndex - 1] : null;
} else if (direction === 'next') {
// return next link in specified array if there is one else return null;
menuLink = array[currentIndex + 1] ? array[currentIndex + 1] : null;
} else {
// return link with same index in specified array;
menuLink = array[currentIndex] ? array[currentIndex] : array[array.length - 1];
}
}
return menuLink;
};
// takes a menu element and allows moving between focus via tabbing/arrows
var setKeyboardNavigation = function setKeyboardNavigation(subMenuContainer, subMenuQuery, onFirst, onLast) {
// array of all links in menu container
var allLinksArray = [];
// use specified query to select all submenus
var subMenus = subMenuContainer.querySelectorAll(subMenuQuery);
subMenus.forEach(function(subMenu, subMenuIndex) {
var thisSubMenuLinks = subMenu.querySelectorAll('a');
// add list of all links in this container to an array
allLinksArray.push(thisSubMenuLinks);
// add event listener to each link with key logic
thisSubMenuLinks.forEach(function(link, linkIndex) {
link.addEventListener('keydown', function(e) {
// if not escape
if (e.keyCode !== 27) {
e.stopPropagation();
if (e.keyCode === 39) {
// right arrow - go to link of same index in next menu list
e.preventDefault();
var nextMenuLink = getMenuLink(linkIndex, allLinksArray[subMenuIndex + 1]);
if (nextMenuLink) nextMenuLink.focus();
} else if (e.keyCode === 37) {
// left arrow - go to link of same index in previous menu list
e.preventDefault();
var prevMenuLink = getMenuLink(linkIndex, allLinksArray[subMenuIndex - 1]);
if (prevMenuLink) prevMenuLink.focus();
} else if (e.keyCode === 40 || e.keyCode === 9 && !e.shiftKey) {
// down arrow or tab - go to next link in current menu list
e.preventDefault();
// if next link doesn't exist try next menu first item else return null
var nextLink = getMenuLink(linkIndex, thisSubMenuLinks, 'next') ? getMenuLink(linkIndex, thisSubMenuLinks, 'next') : getMenuLink(-1, allLinksArray[subMenuIndex + 1], 'next');
if (nextLink) {
nextLink.focus();
} else if (onLast) {
onLast();
}
} else if (e.keyCode === 38 || e.shiftKey && e.keyCode === 9) {
// up arrow or shift + tab - go to previous item in current menu list
e.preventDefault();
var prevMenu = allLinksArray[subMenuIndex - 1];
var prevLink = null;
if (prevMenu || linkIndex > 0) {
prevLink = getMenuLink(linkIndex, thisSubMenuLinks, 'prev') ? getMenuLink(linkIndex, thisSubMenuLinks, 'prev') : getMenuLink(prevMenu.length, prevMenu, 'prev');
}
if (prevLink) {
prevLink.focus();
} else if (onFirst) {
onFirst();
}
}
}
});
});
});
};
megaMenus.forEach(function(menu) {
var clearActiveListItems = function clearActiveListItems() {
// remove active classes from other list items
menu.querySelectorAll('.wmnds-mega-menu__primary-menu-item').forEach(function(menuItem) {
menuItem.classList.remove('active');
});
};
// handle setting the active class on menu and list items
var setMenuActive = function setMenuActive(element) {
var active = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
var onCloseFocusElement = arguments.length > 2 ? arguments[2] : undefined;
if (active) {
menu.classList.add('active');
clearActiveListItems();
// add active class to current item
element.classList.add('active');
} else {
menu.classList.remove('active');
element.classList.remove('active');
// set focus on menu close
if (onCloseFocusElement) onCloseFocusElement.focus();
}
};
// mobile nav function
function handleMobileMenu(mq) {
if (mq.matches) {
var mobileToggle = menu.querySelector('.wmnds-mega-menu__mobile-toggle');
var headerEl = menu.parentNode.parentNode;
var topLevelMenuBtn = menu.querySelectorAll('.wmnds-mega-menu__link-arrow-icon-btn');
var searchBtn = menu.querySelector('.wmnds-mega-menu__search-btn');
// object to see which menu/menu level is open
var mobileMenuIsOpen = {
menu: false,
primary: false,
search: false
};
// handle mobile menu toggle
mobileToggle.addEventListener('click', function() {
mobileMenuIsOpen.menu = !mobileMenuIsOpen.menu;
if (mobileMenuIsOpen.menu) {
mobileMenuIsOpen.search = false;
headerEl.classList.remove('wmnds-header--search-open');
headerEl.classList.add('wmnds-header--mega-menu-open');
document.querySelector('html').classList.add('mobile-menu-open');
} else {
headerEl.classList.remove('wmnds-header--mega-menu-open', 'wmnds-header--mega-menu-submenu-open');
document.querySelector('html').classList.remove('mobile-menu-open');
}
});
if (searchBtn) {
searchBtn.addEventListener('click', function() {
mobileMenuIsOpen.search = !mobileMenuIsOpen.search;
if (mobileMenuIsOpen.search) {
mobileMenuIsOpen.menu = false;
headerEl.classList.remove('wmnds-header--mega-menu-open', 'wmnds-header--mega-menu-submenu-open');
document.querySelector('html').classList.remove('mobile-menu-open');
headerEl.classList.add('wmnds-header--search-open');
} else {
headerEl.classList.remove('wmnds-header--search-open');
}
});
}
// handle sub menu open/close
topLevelMenuBtn.forEach(function(menuBtn) {
var handleSubMenus = function handleSubMenus() {
mobileMenuIsOpen.primary = !mobileMenuIsOpen.primary;
var targetListItem = menuBtn.parentNode;
if (mobileMenuIsOpen.primary) {
targetListItem.classList.add('open');
targetListItem.querySelector('.wmnds-mega-menu__sub-menu-link').focus();
headerEl.classList.add('wmnds-header--mega-menu-submenu-open');
} else {
targetListItem.classList.remove('open');
headerEl.classList.remove('wmnds-header--mega-menu-submenu-open');
}
};
menuBtn.previousElementSibling.addEventListener('click', handleSubMenus);
menuBtn.addEventListener('click', handleSubMenus);
});
// mobile collapse for third level menus
var collapseMenus = menu.querySelectorAll('.wmnds-mega-menu__sub-menu-item .wmnds-mega-menu__collapse-toggle');
collapseMenus.forEach(function(collapseToggle) {
var handleThirdLevelMenus = function handleThirdLevelMenus() {
var panel = collapseToggle.nextElementSibling;
collapseToggle.classList.toggle('open');
if (panel.style.maxHeight) {
panel.style.maxHeight = null;
} else {
panel.style.maxHeight = "".concat(panel.scrollHeight, "px");
}
};
if (collapseToggle.previousElementSibling.tagName !== 'A') {
collapseToggle.previousElementSibling.addEventListener('click', handleThirdLevelMenus);
}
collapseToggle.addEventListener('click', handleThirdLevelMenus);
});
}
}
// end mobile nav function
// init mobile nav function
handleMobileMenu(mobileMenu);
mobileMenu.addListener(handleMobileMenu);
var topLevelLinks = menu.querySelectorAll('.wmnds-mega-menu__primary-menu-link');
var menuDelay = false;
var enterTimeOut;
var leaveTimeOut;
var delayTime = 300;
// handle events within each top level list item
topLevelLinks.forEach(function(topLevelLink, topLevelLinkIndex) {
// return list item parent of the current link if it exists else return the link
var topLevelListItem = topLevelLink.parentNode.tagName === 'LI' || topLevelLink.parentNode.className.includes('wmnds-mega-menu__search') ? topLevelLink.parentNode : topLevelLink;
var subMenuLinks = topLevelListItem.querySelectorAll('.wmnds-mega-menu__sub-menu-link');
// check if level 3 menus are present, if so add modifier class
var hasSubmenuChildren = topLevelListItem.querySelectorAll('.wmnds-mega-menu__sub-menu-child-menu').length !== 0;
if (hasSubmenuChildren) {
topLevelListItem.querySelectorAll('.wmnds-mega-menu__sub-menu').forEach(function(subMenu) {
subMenu.classList.add('wmnds-mega-menu__sub-menu--has-child-menus');
});
}
var openSubMenu = function openSubMenu(e) {
// check if list item has a mega menu
if (topLevelListItem.querySelectorAll('.wmnds-mega-menu__container').length) {
e.preventDefault();
// remove keyFocus to allow menu to show
setMenuActive(topLevelListItem, true);
// focus first menu item
if (topLevelListItem.contains(subMenuLinks[0])) {
subMenuLinks[0].focus();
} else if (topLevelListItem.querySelector('.wmnds-search-bar__input')) {
topLevelListItem.querySelector('.wmnds-search-bar__input').focus();
}
}
};
var handleKeydown = function handleKeydown(e, key) {
e.stopPropagation();
// if key pressed is enter, space bar or down arrow
if (key === 13 || key === 32 || key === 40) {
// enter
// check if link exists
if (key === 13) {
if (!topLevelLink.tagName === 'a' || !topLevelLink.getAttribute('href')) {
openSubMenu(e);
}
} else {
openSubMenu(e);
}
} else if (key === 37) {
// left arrow
var prevLink = getMenuLink(topLevelLinkIndex, topLevelLinks, 'prev');
if (prevLink) prevLink.focus();
} else if (key === 39) {
// right arrow
var nextLink = getMenuLink(topLevelLinkIndex, topLevelLinks, 'next');
if (nextLink) nextLink.focus();
} else if (key === 27) {
// if escape pressed
setMenuActive(topLevelListItem, false, topLevelLink);
}
};
// if top level link doesn't have a mega-menu child add class to menu to hide overlay when hovered
// has to be added/removed on mouseover to cover menus that have a mix of items with/without mega menus
var isTopLevelWithMenu = topLevelListItem.querySelectorAll('.wmnds-mega-menu__container').length;
if (isTopLevelWithMenu) {
topLevelLink.addEventListener('mouseover', function() {
if (!menuDelay) {
// if no menuDelay is active just open the menu
setMenuActive(topLevelListItem);
} else {
// if menuDelay is active, clear all timeouts and start a new one
clearTimeout(enterTimeOut);
clearTimeout(leaveTimeOut);
enterTimeOut = setTimeout(function() {
// enter timeout completed, open menu and kill delay
menuDelay = false;
setMenuActive(topLevelListItem);
}, delayTime);
}
});
topLevelListItem.querySelector('.wmnds-mega-menu__container').addEventListener('mouseover', function() {
if (menuDelay) {
// if container is rehovered before timeout is done, clear all timeouts kill the delay
clearTimeout(enterTimeOut);
clearTimeout(leaveTimeOut);
menuDelay = false;
}
});
topLevelListItem.addEventListener('mouseleave', function() {
menuDelay = true;
// leave timeout is active
leaveTimeOut = setTimeout(function() {
// leave timeout completed, close menu
setMenuActive(topLevelListItem, false);
menuDelay = false;
}, delayTime);
});
topLevelListItem.addEventListener('blur', setMenuActive(topLevelListItem, false));
}
topLevelListItem.addEventListener('keydown', function(e) {
handleKeydown(e, e.keyCode);
});
// top lvl link event listeners
topLevelLink.addEventListener('focus', function(e) {
e.preventDefault();
setMenuActive(topLevelListItem, false);
clearActiveListItems();
});
topLevelLink.addEventListener('mousedown', function(e) {
// prevent link focus on click
e.preventDefault();
});
// set up keyboard navigation for sub menu links
var subMenuContainer = topLevelListItem.querySelector('.wmnds-mega-menu__sub-menu');
if (subMenuContainer) {
setKeyboardNavigation(subMenuContainer, '.wmnds-mega-menu__sub-menu-item',
// what to do on first link
function() {
return topLevelLink.focus();
},
// what to do on last link
function() {
setMenuActive(topLevelListItem, false);
if (getMenuLink(topLevelLinkIndex, topLevelLinks, 'next')) {
getMenuLink(topLevelLinkIndex, topLevelLinks, 'next').focus();
}
});
}
});
// set up keyboard navigation for search menu links
var searchMenuContainer = menu.querySelector('.wmnds-search-container');
if (searchMenuContainer) {
setKeyboardNavigation(searchMenuContainer, '.wmnds-search-list',
// what to do on first link
function() {
return menu.querySelector('.wmnds-search-bar__input').focus();
},
// what to do on last link
function() {
return setMenuActive(menu.querySelector('.wmnds-mega-menu__search'), false, menu.querySelector('.wmnds-mega-menu__search-btn'));
});
}
});
};
var _default = headerJs;
exports["default"] = _default;
Recommended javascript (ES6)
const headerJs = () => {
// get mega menu elements
const megaMenus = document.querySelectorAll('.wmnds-mega-menu');
const mobileMenu = window.matchMedia('(max-width: 767px)');
/*
Mega menu helper functions
*/
// getMenuLink returns a specified menu link from a specified array
// currentIndex = index of the link that is currently focused
// array = array to move through
// direction = next, prev,
const getMenuLink = (currentIndex, array, direction) => {
let menuLink = null;
if (array) {
if (direction === 'prev') {
// return previous link in specified array if there is one else return null;
menuLink = array[currentIndex - 1] ? array[currentIndex - 1] : null;
} else if (direction === 'next') {
// return next link in specified array if there is one else return null;
menuLink = array[currentIndex + 1] ? array[currentIndex + 1] : null;
} else {
// return link with same index in specified array;
menuLink = array[currentIndex] ? array[currentIndex] : array[array.length - 1];
}
}
return menuLink;
};
// takes a menu element and allows moving between focus via tabbing/arrows
const setKeyboardNavigation = (subMenuContainer, subMenuQuery, onFirst, onLast) => {
// array of all links in menu container
const allLinksArray = [];
// use specified query to select all submenus
const subMenus = subMenuContainer.querySelectorAll(subMenuQuery);
subMenus.forEach((subMenu, subMenuIndex) => {
const thisSubMenuLinks = subMenu.querySelectorAll('a');
// add list of all links in this container to an array
allLinksArray.push(thisSubMenuLinks);
// add event listener to each link with key logic
thisSubMenuLinks.forEach((link, linkIndex) => {
link.addEventListener('keydown', e => {
// if not escape
if (e.keyCode !== 27) {
e.stopPropagation();
if (e.keyCode === 39) {
// right arrow - go to link of same index in next menu list
e.preventDefault();
const nextMenuLink = getMenuLink(linkIndex, allLinksArray[subMenuIndex + 1]);
if (nextMenuLink) nextMenuLink.focus();
} else if (e.keyCode === 37) {
// left arrow - go to link of same index in previous menu list
e.preventDefault();
const prevMenuLink = getMenuLink(linkIndex, allLinksArray[subMenuIndex - 1]);
if (prevMenuLink) prevMenuLink.focus();
} else if (e.keyCode === 40 || (e.keyCode === 9 && !e.shiftKey)) {
// down arrow or tab - go to next link in current menu list
e.preventDefault();
// if next link doesn't exist try next menu first item else return null
const nextLink = getMenuLink(linkIndex, thisSubMenuLinks, 'next') ?
getMenuLink(linkIndex, thisSubMenuLinks, 'next') :
getMenuLink(-1, allLinksArray[subMenuIndex + 1], 'next');
if (nextLink) {
nextLink.focus();
} else if (onLast) {
onLast();
}
} else if (e.keyCode === 38 || (e.shiftKey && e.keyCode === 9)) {
// up arrow or shift + tab - go to previous item in current menu list
e.preventDefault();
const prevMenu = allLinksArray[subMenuIndex - 1];
let prevLink = null;
if (prevMenu || linkIndex > 0) {
prevLink = getMenuLink(linkIndex, thisSubMenuLinks, 'prev') ?
getMenuLink(linkIndex, thisSubMenuLinks, 'prev') :
getMenuLink(prevMenu.length, prevMenu, 'prev');
}
if (prevLink) {
prevLink.focus();
} else if (onFirst) {
onFirst();
}
}
}
});
});
});
};
megaMenus.forEach(menu => {
const clearActiveListItems = () => {
// remove active classes from other list items
menu.querySelectorAll('.wmnds-mega-menu__primary-menu-item').forEach(menuItem => {
menuItem.classList.remove('active');
});
};
// handle setting the active class on menu and list items
const setMenuActive = (element, active = true, onCloseFocusElement) => {
if (active) {
menu.classList.add('active');
clearActiveListItems();
// add active class to current item
element.classList.add('active');
} else {
menu.classList.remove('active');
element.classList.remove('active');
// set focus on menu close
if (onCloseFocusElement) onCloseFocusElement.focus();
}
};
// mobile nav function
function handleMobileMenu(mq) {
if (mq.matches) {
const mobileToggle = menu.querySelector('.wmnds-mega-menu__mobile-toggle');
const headerEl = menu.parentNode.parentNode;
const topLevelMenuBtn = menu.querySelectorAll('.wmnds-mega-menu__link-arrow-icon-btn');
const searchBtn = menu.querySelector('.wmnds-mega-menu__search-btn');
// object to see which menu/menu level is open
const mobileMenuIsOpen = {
menu: false,
primary: false,
search: false
};
// handle mobile menu toggle
mobileToggle.addEventListener('click', () => {
mobileMenuIsOpen.menu = !mobileMenuIsOpen.menu;
if (mobileMenuIsOpen.menu) {
mobileMenuIsOpen.search = false;
headerEl.classList.remove('wmnds-header--search-open');
headerEl.classList.add('wmnds-header--mega-menu-open');
document.querySelector('html').classList.add('mobile-menu-open');
} else {
headerEl.classList.remove(
'wmnds-header--mega-menu-open',
'wmnds-header--mega-menu-submenu-open'
);
document.querySelector('html').classList.remove('mobile-menu-open');
}
});
if (searchBtn) {
searchBtn.addEventListener('click', () => {
mobileMenuIsOpen.search = !mobileMenuIsOpen.search;
if (mobileMenuIsOpen.search) {
mobileMenuIsOpen.menu = false;
headerEl.classList.remove(
'wmnds-header--mega-menu-open',
'wmnds-header--mega-menu-submenu-open'
);
document.querySelector('html').classList.remove('mobile-menu-open');
headerEl.classList.add('wmnds-header--search-open');
} else {
headerEl.classList.remove('wmnds-header--search-open');
}
});
}
// handle sub menu open/close
topLevelMenuBtn.forEach(menuBtn => {
const handleSubMenus = () => {
mobileMenuIsOpen.primary = !mobileMenuIsOpen.primary;
const targetListItem = menuBtn.parentNode;
if (mobileMenuIsOpen.primary) {
targetListItem.classList.add('open');
targetListItem.querySelector('.wmnds-mega-menu__sub-menu-link').focus();
headerEl.classList.add('wmnds-header--mega-menu-submenu-open');
} else {
targetListItem.classList.remove('open');
headerEl.classList.remove('wmnds-header--mega-menu-submenu-open');
}
};
menuBtn.previousElementSibling.addEventListener('click', handleSubMenus);
menuBtn.addEventListener('click', handleSubMenus);
});
// mobile collapse for third level menus
const collapseMenus = menu.querySelectorAll(
'.wmnds-mega-menu__sub-menu-item .wmnds-mega-menu__collapse-toggle'
);
collapseMenus.forEach(collapseToggle => {
const handleThirdLevelMenus = () => {
const panel = collapseToggle.nextElementSibling;
collapseToggle.classList.toggle('open');
if (panel.style.maxHeight) {
panel.style.maxHeight = null;
} else {
panel.style.maxHeight = `${panel.scrollHeight}px`;
}
};
if (collapseToggle.previousElementSibling.tagName !== 'A') {
collapseToggle.previousElementSibling.addEventListener('click', handleThirdLevelMenus);
}
collapseToggle.addEventListener('click', handleThirdLevelMenus);
});
}
}
// end mobile nav function
// init mobile nav function
handleMobileMenu(mobileMenu);
mobileMenu.addListener(handleMobileMenu);
const topLevelLinks = menu.querySelectorAll('.wmnds-mega-menu__primary-menu-link');
let menuDelay = false;
let enterTimeOut;
let leaveTimeOut;
const delayTime = 300;
// handle events within each top level list item
topLevelLinks.forEach((topLevelLink, topLevelLinkIndex) => {
// return list item parent of the current link if it exists else return the link
const topLevelListItem =
topLevelLink.parentNode.tagName === 'LI' ||
topLevelLink.parentNode.className.includes('wmnds-mega-menu__search') ?
topLevelLink.parentNode :
topLevelLink;
const subMenuLinks = topLevelListItem.querySelectorAll('.wmnds-mega-menu__sub-menu-link');
// check if level 3 menus are present, if so add modifier class
const hasSubmenuChildren =
topLevelListItem.querySelectorAll('.wmnds-mega-menu__sub-menu-child-menu').length !== 0;
if (hasSubmenuChildren) {
topLevelListItem.querySelectorAll('.wmnds-mega-menu__sub-menu').forEach(subMenu => {
subMenu.classList.add('wmnds-mega-menu__sub-menu--has-child-menus');
});
}
const openSubMenu = e => {
// check if list item has a mega menu
if (topLevelListItem.querySelectorAll('.wmnds-mega-menu__container').length) {
e.preventDefault();
// remove keyFocus to allow menu to show
setMenuActive(topLevelListItem, true);
// focus first menu item
if (topLevelListItem.contains(subMenuLinks[0])) {
subMenuLinks[0].focus();
} else if (topLevelListItem.querySelector('.wmnds-search-bar__input')) {
topLevelListItem.querySelector('.wmnds-search-bar__input').focus();
}
}
};
const handleKeydown = (e, key) => {
e.stopPropagation();
// if key pressed is enter, space bar or down arrow
if (key === 13 || key === 32 || key === 40) {
// enter
// check if link exists
if (key === 13) {
if (!topLevelLink.tagName === 'a' || !topLevelLink.getAttribute('href')) {
openSubMenu(e);
}
} else {
openSubMenu(e);
}
} else if (key === 37) {
// left arrow
const prevLink = getMenuLink(topLevelLinkIndex, topLevelLinks, 'prev');
if (prevLink) prevLink.focus();
} else if (key === 39) {
// right arrow
const nextLink = getMenuLink(topLevelLinkIndex, topLevelLinks, 'next');
if (nextLink) nextLink.focus();
} else if (key === 27) {
// if escape pressed
setMenuActive(topLevelListItem, false, topLevelLink);
}
};
// if top level link doesn't have a mega-menu child add class to menu to hide overlay when hovered
// has to be added/removed on mouseover to cover menus that have a mix of items with/without mega menus
const isTopLevelWithMenu = topLevelListItem.querySelectorAll(
'.wmnds-mega-menu__container'
).length;
if (isTopLevelWithMenu) {
topLevelLink.addEventListener('mouseover', () => {
if (!menuDelay) {
// if no menuDelay is active just open the menu
setMenuActive(topLevelListItem);
} else {
// if menuDelay is active, clear all timeouts and start a new one
clearTimeout(enterTimeOut);
clearTimeout(leaveTimeOut);
enterTimeOut = setTimeout(() => {
// enter timeout completed, open menu and kill delay
menuDelay = false;
setMenuActive(topLevelListItem);
}, delayTime);
}
});
topLevelListItem
.querySelector('.wmnds-mega-menu__container')
.addEventListener('mouseover', () => {
if (menuDelay) {
// if container is rehovered before timeout is done, clear all timeouts kill the delay
clearTimeout(enterTimeOut);
clearTimeout(leaveTimeOut);
menuDelay = false;
}
});
topLevelListItem.addEventListener('mouseleave', () => {
menuDelay = true;
// leave timeout is active
leaveTimeOut = setTimeout(() => {
// leave timeout completed, close menu
setMenuActive(topLevelListItem, false);
menuDelay = false;
}, delayTime);
});
topLevelListItem.addEventListener('blur', setMenuActive(topLevelListItem, false));
}
topLevelListItem.addEventListener('keydown', e => {
handleKeydown(e, e.keyCode);
});
// top lvl link event listeners
topLevelLink.addEventListener('focus', e => {
e.preventDefault();
setMenuActive(topLevelListItem, false);
clearActiveListItems();
});
topLevelLink.addEventListener('mousedown', e => {
// prevent link focus on click
e.preventDefault();
});
// set up keyboard navigation for sub menu links
const subMenuContainer = topLevelListItem.querySelector('.wmnds-mega-menu__sub-menu');
if (subMenuContainer) {
setKeyboardNavigation(
subMenuContainer,
'.wmnds-mega-menu__sub-menu-item',
// what to do on first link
() => topLevelLink.focus(),
// what to do on last link
() => {
setMenuActive(topLevelListItem, false);
if (getMenuLink(topLevelLinkIndex, topLevelLinks, 'next')) {
getMenuLink(topLevelLinkIndex, topLevelLinks, 'next').focus();
}
}
);
}
});
// set up keyboard navigation for search menu links
const searchMenuContainer = menu.querySelector('.wmnds-search-container');
if (searchMenuContainer) {
setKeyboardNavigation(
searchMenuContainer,
'.wmnds-search-list',
// what to do on first link
() => menu.querySelector('.wmnds-search-bar__input').focus(),
// what to do on last link
() =>
setMenuActive(
menu.querySelector('.wmnds-mega-menu__search'),
false,
menu.querySelector('.wmnds-mega-menu__search-btn')
)
);
}
});
};
export default headerJs;
Mega menu with search example
HTML markup
<!-- Skip to content link -->
<a href="#wmnds-main-content" title="Skip to main content" target="_self" class="wmnds-link wmnds-header__skip-link">
Skip to main content
</a>
<!-- Main header section -->
<header>
<div class="wmnds-bg-white wmnds-p-t-md wmnds-p-b-md wmnds-cookies-banner">
<div class="wmnds-container">
<div class="wmnds-col-1 wmnds-col-md-3-4 wmnds-col-lg-2-3">
<h3>Your privacy settings</h3>
<p>
We use cookies to help you with journey planning and relevant disruptions, remember your login and show you
content you might be interested in. If you’re happy with the use of cookies by West Midlands Combined Authority
and our selected partners, click ‘Accept all cookies’. Or click ‘Manage cookies’ to learn more.
</p>
<div class="wmnds-grid wmnds-grid--justify-between wmnds-cookies-banner__group-buttons">
<button class="wmnds-btn wmnds-col-1 wmnds-col-sm-1 wmnds-col-md-12-24 wmnds-cookies-banner__accept-all-cookies wmnds-text-align-center" type="button">
Accept all cookies
</button>
<a href="https://www.tfwm.org.uk/manage-cookies/" title="link title" target="_self" class="wmnds-btn wmnds-btn wmnds-col-1 wmnds-col-sm-1 wmnds-col-md-12-24 wmnds-text-align-center">
Manage Cookies
</a>
</div>
</div>
</div>
</div>
<div class="wmnds-header wmnds-header--mega-menu">
<div class="wmnds-container wmnds-grid wmnds-grid--align-center wmnds-grid--justify-between">
<div class="wmnds-header__vertical-align wmnds-col-auto">
<!-- Logo -->
<a class="wmnds-header__logo-link" href="/" title="Transport for West Midlands Design System">
<img class="wmnds-header__logo" alt="Transport for West Midlands logo" src="/img/logo.svg" />
</a>
</div>
<!-- Mega menu nav items-->
<nav id="mega-menu-example" class="wmnds-mega-menu">
<!-- Mobile toggle button -->
<button class="wmnds-mega-menu__mobile-toggle wmnds-btn wmnds-btn--secondary wmnds-hide-desktop" aria-expanded="false" aria-controls="mega-menu-example-primary-menu">Menu
<svg class="wmnds-mega-menu__close-icon">
<use xlink:href="#wmnds-general-cross" href="#wmnds-general-cross"></use>
</svg>
</button>
<!-- Container for mega menu - allows scrolling on mobile nav -->
<div id="mega-menu-example-primary-menu" class="wmnds-mega-menu__scroll-controller">
<!-- Start primary (level 1) navigation -->
<ul class="wmnds-mega-menu__primary-menu">
<li class="wmnds-mega-menu__primary-menu-item">
<!-- Show swift logo in nav if is swift & tickets link -->
<!-- allow primary (level 1) item to link if specified -->
<button target="_self" class="wmnds-mega-menu__primary-menu-link">
Plan a journey
</button>
<!-- arrow icon for mobile nav -->
<button class="wmnds-mega-menu__link-arrow-icon-btn" data-label="Plan a journey" aria-label="toggle Plan a journey" aria-controls="mega-menu-example-container"><svg class="wmnds-mega-menu__link-arrow-icon">
<use xlink-href="#wmnds-general-chevron-right" href="#wmnds-general-chevron-right"></use>
</svg></button>
<!-- Start mega menu container -->
<div id="mega-menu-example-container-1" class="wmnds-mega-menu__container">
<div class="wmnds-container">
<!-- Start submenu (level 2) -->
<ul class="wmnds-mega-menu__sub-menu">
<li class="wmnds-mega-menu__sub-menu-item">
<a href="/plan-a-journey/plan-your-journey/" class="wmnds-mega-menu__sub-menu-link">
<!-- Show submenu (level 2) item icon if specified -->
<svg class="wmnds-mega-menu__sub-menu-link-icon">
<use xlink:href="#wmnds-general-route" href="#wmnds-general-route"></use>
</svg>
Plan your journey</a>
<!-- collapse button for mobile nav level 3 menu items -->
<button class="wmnds-mega-menu__collapse-toggle" aria-expanded="false" aria-label="Toggle Plan your journey menu" aria-controls="mega-menu-example-submenu-child-menu"><svg class="wmnds-mega-menu__link-arrow-icon">
<use xlink-href="#wmnds-general-chevron-right" href="#wmnds-general-chevron-right"></use>
</svg></button>
<!-- Start submenu child (level 3) list -->
<ul id="mega-menu-example-submenu-child-menu-plan-your-journey-1" class="wmnds-mega-menu__sub-menu-child-menu">
<li class="wmnds-mega-menu__sub-menu-child-item">
<a href="/plan-a-journey/plan-your-journey/journey-planner/" class="wmnds-mega-menu__sub-menu-child-link">Journey planner</a>
</li>
<li class="wmnds-mega-menu__sub-menu-child-item">
<a href="/plan-a-journey/plan-your-journey/find-a-timetable/" class="wmnds-mega-menu__sub-menu-child-link">Find a timetable</a>
</li>
<li class="wmnds-mega-menu__sub-menu-child-item">
<a href="/plan-a-journey/plan-your-journey/live-and-planned-disruptions/" class="wmnds-mega-menu__sub-menu-child-link">Live and planned disruptions</a>
</li>
<li class="wmnds-mega-menu__sub-menu-child-item">
<a href="/plan-a-journey/plan-your-journey/major-roadworks-and-events/" class="wmnds-mega-menu__sub-menu-child-link">Major roadworks and events</a>
</li>
</ul>
<!-- End submenu child (level 3) list -->
</li>
<li class="wmnds-mega-menu__sub-menu-item">
<a href="/plan-a-journey/ways-to-travel/" class="wmnds-mega-menu__sub-menu-link">
<!-- Show submenu (level 2) item icon if specified -->
<svg class="wmnds-mega-menu__sub-menu-link-icon">
<use xlink:href="#wmnds-general-location-arrow" href="#wmnds-general-location-arrow"></use>
</svg>
Ways to travel</a>
<!-- collapse button for mobile nav level 3 menu items -->
<button class="wmnds-mega-menu__collapse-toggle" aria-expanded="false" aria-label="Toggle Ways to travel menu" aria-controls="mega-menu-example-submenu-child-menu"><svg class="wmnds-mega-menu__link-arrow-icon">
<use xlink-href="#wmnds-general-chevron-right" href="#wmnds-general-chevron-right"></use>
</svg></button>
<!-- Start submenu child (level 3) list -->
<ul id="mega-menu-example-submenu-child-menu-ways-to-travel-2" class="wmnds-mega-menu__sub-menu-child-menu">
<li class="wmnds-mega-menu__sub-menu-child-item">
<a href="/plan-a-journey/ways-to-travel/walking/" class="wmnds-mega-menu__sub-menu-child-link">Walking</a>
</li>
<li class="wmnds-mega-menu__sub-menu-child-item">
<a href="/plan-a-journey/ways-to-travel/cycling/" class="wmnds-mega-menu__sub-menu-child-link">Cycling</a>
</li>
<li class="wmnds-mega-menu__sub-menu-child-item">
<a href="/plan-a-journey/ways-to-travel/buses/" class="wmnds-mega-menu__sub-menu-child-link">Buses</a>
</li>
<li class="wmnds-mega-menu__sub-menu-child-item">
<a href="/plan-a-journey/ways-to-travel/trains/" class="wmnds-mega-menu__sub-menu-child-link">Trains</a>
</li>
<li class="wmnds-mega-menu__sub-menu-child-item">
<a href="/plan-a-journey/ways-to-travel/trams/" class="wmnds-mega-menu__sub-menu-child-link">Trams</a>
</li>
<li class="wmnds-mega-menu__sub-menu-child-item">
<a href="/plan-a-journey/ways-to-travel/park-and-ride/" class="wmnds-mega-menu__sub-menu-child-link">Park and ride</a>
</li>
<li class="wmnds-mega-menu__sub-menu-child-item">
<a href="/plan-a-journey/ways-to-travel/driving/" class="wmnds-mega-menu__sub-menu-child-link">Driving</a>
</li>
</ul>
<!-- End submenu child (level 3) list -->
</li>
<li class="wmnds-mega-menu__sub-menu-item">
<a href="/plan-a-journey/travel-information/" class="wmnds-mega-menu__sub-menu-link">
<!-- Show submenu (level 2) item icon if specified -->
<svg class="wmnds-mega-menu__sub-menu-link-icon">
<use xlink:href="#wmnds-general-info" href="#wmnds-general-info"></use>
</svg>
Travel information</a>
<!-- collapse button for mobile nav level 3 menu items -->
<button class="wmnds-mega-menu__collapse-toggle" aria-expanded="false" aria-label="Toggle Travel information menu" aria-controls="mega-menu-example-submenu-child-menu"><svg class="wmnds-mega-menu__link-arrow-icon">
<use xlink-href="#wmnds-general-chevron-right" href="#wmnds-general-chevron-right"></use>
</svg></button>
<!-- Start submenu child (level 3) list -->
<ul id="mega-menu-example-submenu-child-menu-travel-information-3" class="wmnds-mega-menu__sub-menu-child-menu">
<li class="wmnds-mega-menu__sub-menu-child-item">
<a href="/plan-a-journey/travel-information/how-to-travel-with-accessibility-needs/" class="wmnds-mega-menu__sub-menu-child-link">How to travel with accessibility needs</a>
</li>
<li class="wmnds-mega-menu__sub-menu-child-item">
<a href="/plan-a-journey/travel-information/how-to-travel-safely/" class="wmnds-mega-menu__sub-menu-child-link">How to travel safely</a>
</li>
<li class="wmnds-mega-menu__sub-menu-child-item">
<a href="/plan-a-journey/travel-information/how-to-get-to-hospital/" class="wmnds-mega-menu__sub-menu-child-link">How to get to hospital</a>
</li>
</ul>
<!-- End submenu child (level 3) list -->
</li>
<li class="wmnds-mega-menu__sub-menu-item">
<a href="/plan-a-journey/discover-our-region/" class="wmnds-mega-menu__sub-menu-link">
<!-- Show submenu (level 2) item icon if specified -->
<svg class="wmnds-mega-menu__sub-menu-link-icon">
<use xlink:href="#wmnds-general-star" href="#wmnds-general-star"></use>
</svg>
Discover our region</a>
<!-- collapse button for mobile nav level 3 menu items -->
<button class="wmnds-mega-menu__collapse-toggle" aria-expanded="false" aria-label="Toggle Discover our region menu" aria-controls="mega-menu-example-submenu-child-menu"><svg class="wmnds-mega-menu__link-arrow-icon">
<use xlink-href="#wmnds-general-chevron-right" href="#wmnds-general-chevron-right"></use>
</svg></button>
<!-- Start submenu child (level 3) list -->
<ul id="mega-menu-example-submenu-child-menu-discover-our-region-4" class="wmnds-mega-menu__sub-menu-child-menu">
<li class="wmnds-mega-menu__sub-menu-child-item">
<a href="/plan-a-journey/discover-our-region/places-and-attractions-to-visit/" class="wmnds-mega-menu__sub-menu-child-link">Places and attractions to visit</a>
</li>
<li class="wmnds-mega-menu__sub-menu-child-item">
<a href="/plan-a-journey/discover-our-region/find-discount-vouchers/" class="wmnds-mega-menu__sub-menu-child-link">Find discount vouchers</a>
</li>
</ul>
<!-- End submenu child (level 3) list -->
</li>
</ul>
<!-- End submenu (level 2) -->
</div>
</div>
<!-- End mega menu container -->
</li>
<li class="wmnds-mega-menu__primary-menu-item">
<!-- Show swift logo in nav if is swift & tickets link -->
<!-- allow primary (level 1) item to link if specified -->
<button target="_self" class="wmnds-mega-menu__primary-menu-link">
<svg class="wmnds-swift-icon">
<title>Swift</title>
<use xlink:href="#wmnds-swift-full-logo" href="#wmnds-swift-full-logo"></use>
</svg>
and tickets
</button>
<!-- arrow icon for mobile nav -->
<button class="wmnds-mega-menu__link-arrow-icon-btn" data-label="Swift and tickets" aria-label="toggle Swift and tickets" aria-controls="mega-menu-example-container"><svg class="wmnds-mega-menu__link-arrow-icon">
<use xlink-href="#wmnds-general-chevron-right" href="#wmnds-general-chevron-right"></use>
</svg></button>
<!-- Start mega menu container -->
<div id="mega-menu-example-container-2" class="wmnds-mega-menu__container">
<div class="wmnds-container">
<!-- Start submenu (level 2) -->
<ul class="wmnds-mega-menu__sub-menu">
<li class="wmnds-mega-menu__sub-menu-item">
<a href="/swift-and-tickets/find-a-ticket/" class="wmnds-mega-menu__sub-menu-link">
<!-- Show submenu (level 2) item icon if specified -->
<svg class="wmnds-mega-menu__sub-menu-link-icon">
<use xlink:href="#wmnds-general-search" href="#wmnds-general-search"></use>
</svg>
Find a ticket</a>
<!-- collapse button for mobile nav level 3 menu items -->
<button class="wmnds-mega-menu__collapse-toggle" aria-expanded="false" aria-label="Toggle Find a ticket menu" aria-controls="mega-menu-example-submenu-child-menu"><svg class="wmnds-mega-menu__link-arrow-icon">
<use xlink-href="#wmnds-general-chevron-right" href="#wmnds-general-chevron-right"></use>
</svg></button>
<!-- Start submenu child (level 3) list -->
<ul id="mega-menu-example-submenu-child-menu-find-a-ticket-1" class="wmnds-mega-menu__sub-menu-child-menu">
<li class="wmnds-mega-menu__sub-menu-child-item">
<a href="/swift-and-tickets/find-a-ticket/ticket-finder/" class="wmnds-mega-menu__sub-menu-child-link">Ticket finder</a>
</li>
<li class="wmnds-mega-menu__sub-menu-child-item">
<a href="/swift-and-tickets/find-a-ticket/types-of-ticket/" class="wmnds-mega-menu__sub-menu-child-link">Types of ticket</a>
</li>
</ul>
<!-- End submenu child (level 3) list -->
</li>
<li class="wmnds-mega-menu__sub-menu-item">
<a href="/swift-and-tickets/discounts-and-free-travel-passes/" class="wmnds-mega-menu__sub-menu-link">
<!-- Show submenu (level 2) item icon if specified -->
<svg class="wmnds-mega-menu__sub-menu-link-icon">
<use xlink:href="#wmnds-general-wallet" href="#wmnds-general-wallet"></use>
</svg>
Discounts and free travel passes</a>
<!-- collapse button for mobile nav level 3 menu items -->
<button class="wmnds-mega-menu__collapse-toggle" aria-expanded="false" aria-label="Toggle Discounts and free travel passes menu" aria-controls="mega-menu-example-submenu-child-menu"><svg class="wmnds-mega-menu__link-arrow-icon">
<use xlink-href="#wmnds-general-chevron-right" href="#wmnds-general-chevron-right"></use>
</svg></button>
<!-- Start submenu child (level 3) list -->
<ul id="mega-menu-example-submenu-child-menu-discounts-and-free-travel-passes-2" class="wmnds-mega-menu__sub-menu-child-menu">
<li class="wmnds-mega-menu__sub-menu-child-item">
<a href="/swift-and-tickets/discounts-and-free-travel-passes/under-18s/" class="wmnds-mega-menu__sub-menu-child-link">Under 18s</a>
</li>
<li class="wmnds-mega-menu__sub-menu-child-item">
<a href="/swift-and-tickets/discounts-and-free-travel-passes/students/" class="wmnds-mega-menu__sub-menu-child-link">Students</a>
</li>
<li class="wmnds-mega-menu__sub-menu-child-item">
<a href="/swift-and-tickets/discounts-and-free-travel-passes/families-and-groups/" class="wmnds-mega-menu__sub-menu-child-link">Families and groups</a>
</li>
<li class="wmnds-mega-menu__sub-menu-child-item">
<a href="/swift-and-tickets/discounts-and-free-travel-passes/finding-or-starting-a-new-job/" class="wmnds-mega-menu__sub-menu-child-link">Finding or starting a new job</a>
</li>
<li class="wmnds-mega-menu__sub-menu-child-item">
<a href="/swift-and-tickets/discounts-and-free-travel-passes/disabled-person's-pass/" class="wmnds-mega-menu__sub-menu-child-link">Disabled person's pass</a>
</li>
<li class="wmnds-mega-menu__sub-menu-child-item">
<a href="/swift-and-tickets/discounts-and-free-travel-passes/older-person's-pass/" class="wmnds-mega-menu__sub-menu-child-link">Older person's pass</a>
</li>
<li class="wmnds-mega-menu__sub-menu-child-item">
<a href="/swift-and-tickets/discounts-and-free-travel-passes/waspi-women's-pass/" class="wmnds-mega-menu__sub-menu-child-link">Waspi women's pass</a>
</li>
</ul>
<!-- End submenu child (level 3) list -->
</li>
<li class="wmnds-mega-menu__sub-menu-item">
<a href="/swift-and-tickets/manage-your-swift-card/" class="wmnds-mega-menu__sub-menu-link">
<!-- Show submenu (level 2) item icon if specified -->
<svg class="wmnds-mega-menu__sub-menu-link-icon">
<use xlink:href="#wmnds-swift-bird-icon" href="#wmnds-swift-bird-icon"></use>
</svg>
Manage your <svg class="wmnds-swift-icon">
<title>Swift</title>
<use xlink:href="#wmnds-swift-full-logo" href="#wmnds-swift-full-logo"></use>
</svg> card</a>
<!-- collapse button for mobile nav level 3 menu items -->
<button class="wmnds-mega-menu__collapse-toggle" aria-expanded="false" aria-label="Toggle Manage your Swift card menu" aria-controls="mega-menu-example-submenu-child-menu"><svg class="wmnds-mega-menu__link-arrow-icon">
<use xlink-href="#wmnds-general-chevron-right" href="#wmnds-general-chevron-right"></use>
</svg></button>
<!-- Start submenu child (level 3) list -->
<ul id="mega-menu-example-submenu-child-menu-manage-your-swift-card-3" class="wmnds-mega-menu__sub-menu-child-menu">
<li class="wmnds-mega-menu__sub-menu-child-item">
<a href="/swift-and-tickets/manage-your-swift-card/how-to-use-your-swift-card/" class="wmnds-mega-menu__sub-menu-child-link">How to use your swift card</a>
</li>
<li class="wmnds-mega-menu__sub-menu-child-item">
<a href="/swift-and-tickets/manage-your-swift-card/update-your-personal-details/" class="wmnds-mega-menu__sub-menu-child-link">Update your personal details</a>
</li>
<li class="wmnds-mega-menu__sub-menu-child-item">
<a href="/swift-and-tickets/manage-your-swift-card/top-up-your-swift-card/" class="wmnds-mega-menu__sub-menu-child-link">Top up your swift card</a>
</li>
<li class="wmnds-mega-menu__sub-menu-child-item">
<a href="/swift-and-tickets/manage-your-swift-card/view-your-payment-history/" class="wmnds-mega-menu__sub-menu-child-link">View your payment history</a>
</li>
<li class="wmnds-mega-menu__sub-menu-child-item">
<a href="/swift-and-tickets/manage-your-swift-card/view-your-travel-history/" class="wmnds-mega-menu__sub-menu-child-link">View your travel history</a>
</li>
<li class="wmnds-mega-menu__sub-menu-child-item">
<a href="/swift-and-tickets/manage-your-swift-card/replace-your-lost-swift-card/" class="wmnds-mega-menu__sub-menu-child-link">Replace your lost swift card</a>
</li>
</ul>
<!-- End submenu child (level 3) list -->
</li>
<li class="wmnds-mega-menu__sub-menu-item">
<a href="/swift-and-tickets/manage-your-ticket/" class="wmnds-mega-menu__sub-menu-link">
<!-- Show submenu (level 2) item icon if specified -->
<svg class="wmnds-mega-menu__sub-menu-link-icon">
<use xlink:href="#wmnds-general-ticket" href="#wmnds-general-ticket"></use>
</svg>
Manage your ticket</a>
<!-- collapse button for mobile nav level 3 menu items -->
<button class="wmnds-mega-menu__collapse-toggle" aria-expanded="false" aria-label="Toggle Manage your ticket menu" aria-controls="mega-menu-example-submenu-child-menu"><svg class="wmnds-mega-menu__link-arrow-icon">
<use xlink-href="#wmnds-general-chevron-right" href="#wmnds-general-chevron-right"></use>
</svg></button>
<!-- Start submenu child (level 3) list -->
<ul id="mega-menu-example-submenu-child-menu-manage-your-ticket-4" class="wmnds-mega-menu__sub-menu-child-menu">
<li class="wmnds-mega-menu__sub-menu-child-item">
<a href="/swift-and-tickets/manage-your-ticket/update-your-direct-debit/" class="wmnds-mega-menu__sub-menu-child-link">Update your direct debit</a>
</li>
<li class="wmnds-mega-menu__sub-menu-child-item">
<a href="/swift-and-tickets/manage-your-ticket/get-a-refund/" class="wmnds-mega-menu__sub-menu-child-link">Get a refund</a>
</li>
<li class="wmnds-mega-menu__sub-menu-child-item">
<a href="/swift-and-tickets/manage-your-ticket/make-a-payment/" class="wmnds-mega-menu__sub-menu-child-link">Make a payment</a>
</li>
<li class="wmnds-mega-menu__sub-menu-child-item">
<a href="/swift-and-tickets/manage-your-ticket/replace-your-lost-ticket-or-pass/" class="wmnds-mega-menu__sub-menu-child-link">Replace your lost ticket or pass</a>
</li>
</ul>
<!-- End submenu child (level 3) list -->
</li>
</ul>
<!-- End submenu (level 2) -->
</div>
</div>
<!-- End mega menu container -->
</li>
<li class="wmnds-mega-menu__primary-menu-item">
<!-- Show swift logo in nav if is swift & tickets link -->
<!-- allow primary (level 1) item to link if specified -->
<button target="_self" class="wmnds-mega-menu__primary-menu-link">
Get help
</button>
<!-- arrow icon for mobile nav -->
<button class="wmnds-mega-menu__link-arrow-icon-btn" data-label="Get help" aria-label="toggle Get help" aria-controls="mega-menu-example-container"><svg class="wmnds-mega-menu__link-arrow-icon">
<use xlink-href="#wmnds-general-chevron-right" href="#wmnds-general-chevron-right"></use>
</svg></button>
<!-- Start mega menu container -->
<div id="mega-menu-example-container-3" class="wmnds-mega-menu__container">
<div class="wmnds-container">
<!-- Start submenu (level 2) -->
<ul class="wmnds-mega-menu__sub-menu">
<li class="wmnds-mega-menu__sub-menu-item">
<a href="/get-help/contact-an-operator/" class="wmnds-mega-menu__sub-menu-link">
Contact an operator</a>
</li>
<li class="wmnds-mega-menu__sub-menu-item">
<a href="/get-help/find-a-travel-centre/" class="wmnds-mega-menu__sub-menu-link">
Find a travel centre</a>
</li>
<li class="wmnds-mega-menu__sub-menu-item">
<a href="/get-help/find-lost-property/" class="wmnds-mega-menu__sub-menu-link">
Find lost property</a>
</li>
<li class="wmnds-mega-menu__sub-menu-item">
<a href="/get-help/give-feedback/" class="wmnds-mega-menu__sub-menu-link">
Give feedback</a>
</li>
<li class="wmnds-mega-menu__sub-menu-item">
<a href="/get-help/contact-us/" class="wmnds-mega-menu__sub-menu-link">
Contact us</a>
</li>
<li class="wmnds-mega-menu__sub-menu-item">
<a href="/get-help/report-anti-social-behaviour/" class="wmnds-mega-menu__sub-menu-link">
Report anti-social behaviour</a>
</li>
<li class="wmnds-mega-menu__sub-menu-item">
<a href="/get-help/request-a-new-bus-shelter-or-stop-pole/" class="wmnds-mega-menu__sub-menu-link">
Request a new bus shelter or stop pole</a>
</li>
<li class="wmnds-mega-menu__sub-menu-item">
<a href="/get-help/report-a-problem-with-a-shelter-or-stop/" class="wmnds-mega-menu__sub-menu-link">
Report a problem with a shelter or stop</a>
</li>
</ul>
<!-- End submenu (level 2) -->
</div>
</div>
<!-- End mega menu container -->
</li>
</ul>
<!-- End primary (level 1) navigation -->
</div>
<!-- End scrollable container -->
<div class="wmnds-mega-menu__primary-menu-item wmnds-mega-menu__search">
<button class="wmnds-mega-menu__primary-menu-link wmnds-mega-menu__search-btn">
<svg class="wmnds-mega-menu__search-icon">
<title>Search</title>
<use xlink:href="#wmnds-general-search" href="#wmnds-general-search"></use>
</svg>
<svg class="wmnds-mega-menu__search-close-icon">
<title>Close</title>
<use xlink:href="#wmnds-general-cross" href="#wmnds-general-cross"></use>
</svg>
</button>
<div class="wmnds-mega-menu__container wmnds-mega-menu__search-container">
<div class="wmnds-search-container">
<form id="searchBar_form" class="wmnds-search-bar">
<input id="searchBar_input" aria-label="Search" type="text" class="wmnds-search-bar__input wmnds-fe-input" placeholder="Search for tickets, timetables, travel advice…">
<button class="wmnds-search-bar__btn" type="submit">
<svg>
<title>Search</title>
<use xlink:href="#wmnds-general-search" href="#wmnds-general-search"></use>
</svg>
</button>
</form>
<p class="wmnds-search-heading wmnds-h1">I want to...</p>
<div class="wmnds-grid">
<div class="wmnds-col-1-2">
<ul class="wmnds-search-list">
<li class="wmnds-search-list__item">
<a href="#" class="wmnds-link wmnds-link--with-chevron">See live departures
<svg class="wmnds-link__chevron wmnds-link__chevron--right">
<use xlink:href="#wmnds-general-chevron-right" href="#wmnds-general-chevron-right"></use>
</svg>
</a>
</li>
<li class="wmnds-search-list__item">
<a href="#" class="wmnds-link wmnds-link--with-chevron">Find a timetable
<svg class="wmnds-link__chevron wmnds-link__chevron--right">
<use xlink:href="#wmnds-general-chevron-right" href="#wmnds-general-chevron-right"></use>
</svg>
</a>
</li>
<li class="wmnds-search-list__item">
<a href="#" class="wmnds-link wmnds-link--with-chevron">Check for disruptions
<svg class="wmnds-link__chevron wmnds-link__chevron--right">
<use xlink:href="#wmnds-general-chevron-right" href="#wmnds-general-chevron-right"></use>
</svg>
</a>
</li>
<li class="wmnds-search-list__item">
<a href="#" class="wmnds-link wmnds-link--with-chevron">Manage my Swift card
<svg class="wmnds-link__chevron wmnds-link__chevron--right">
<use xlink:href="#wmnds-general-chevron-right" href="#wmnds-general-chevron-right"></use>
</svg>
</a>
</li>
</ul>
</div>
<div class="wmnds-col-1-2">
<ul class="wmnds-search-list">
<li class="wmnds-search-list__item">
<a href="#" class="wmnds-link wmnds-link--with-chevron">Find ticket prices
<svg class="wmnds-link__chevron wmnds-link__chevron--right">
<use xlink:href="#wmnds-general-chevron-right" href="#wmnds-general-chevron-right"></use>
</svg>
</a>
</li>
<li class="wmnds-search-list__item">
<a href="#" class="wmnds-link wmnds-link--with-chevron">Get a refund
<svg class="wmnds-link__chevron wmnds-link__chevron--right">
<use xlink:href="#wmnds-general-chevron-right" href="#wmnds-general-chevron-right"></use>
</svg>
</a>
</li>
<li class="wmnds-search-list__item">
<a href="#" class="wmnds-link wmnds-link--with-chevron">Update Direct Debit
<svg class="wmnds-link__chevron wmnds-link__chevron--right">
<use xlink:href="#wmnds-general-chevron-right" href="#wmnds-general-chevron-right"></use>
</svg>
</a>
</li>
<li class="wmnds-search-list__item">
<a href="#" class="wmnds-link wmnds-link--with-chevron">Apply for a 16-18 photocard
<svg class="wmnds-link__chevron wmnds-link__chevron--right">
<use xlink:href="#wmnds-general-chevron-right" href="#wmnds-general-chevron-right"></use>
</svg>
</a>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</nav>
</div>
</div>
</header>
<!-- Content space for example purposes only. DO NOT INCLUDE -->
<div class="demo-content">
<p>Example content <br> <a href="/patterns/header-demo" target="_blank">View full page example (opens in new window)</a></p>
</div>
<!-- END DO NOT INCLUDE -->
Recommended javascript (browser compatible)
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports["default"] = void 0;
var headerJs = function headerJs() {
// get mega menu elements
var megaMenus = document.querySelectorAll('.wmnds-mega-menu');
var mobileMenu = window.matchMedia('(max-width: 767px)');
/*
Mega menu helper functions
*/
// getMenuLink returns a specified menu link from a specified array
// currentIndex = index of the link that is currently focused
// array = array to move through
// direction = next, prev,
var getMenuLink = function getMenuLink(currentIndex, array, direction) {
var menuLink = null;
if (array) {
if (direction === 'prev') {
// return previous link in specified array if there is one else return null;
menuLink = array[currentIndex - 1] ? array[currentIndex - 1] : null;
} else if (direction === 'next') {
// return next link in specified array if there is one else return null;
menuLink = array[currentIndex + 1] ? array[currentIndex + 1] : null;
} else {
// return link with same index in specified array;
menuLink = array[currentIndex] ? array[currentIndex] : array[array.length - 1];
}
}
return menuLink;
};
// takes a menu element and allows moving between focus via tabbing/arrows
var setKeyboardNavigation = function setKeyboardNavigation(subMenuContainer, subMenuQuery, onFirst, onLast) {
// array of all links in menu container
var allLinksArray = [];
// use specified query to select all submenus
var subMenus = subMenuContainer.querySelectorAll(subMenuQuery);
subMenus.forEach(function(subMenu, subMenuIndex) {
var thisSubMenuLinks = subMenu.querySelectorAll('a');
// add list of all links in this container to an array
allLinksArray.push(thisSubMenuLinks);
// add event listener to each link with key logic
thisSubMenuLinks.forEach(function(link, linkIndex) {
link.addEventListener('keydown', function(e) {
// if not escape
if (e.keyCode !== 27) {
e.stopPropagation();
if (e.keyCode === 39) {
// right arrow - go to link of same index in next menu list
e.preventDefault();
var nextMenuLink = getMenuLink(linkIndex, allLinksArray[subMenuIndex + 1]);
if (nextMenuLink) nextMenuLink.focus();
} else if (e.keyCode === 37) {
// left arrow - go to link of same index in previous menu list
e.preventDefault();
var prevMenuLink = getMenuLink(linkIndex, allLinksArray[subMenuIndex - 1]);
if (prevMenuLink) prevMenuLink.focus();
} else if (e.keyCode === 40 || e.keyCode === 9 && !e.shiftKey) {
// down arrow or tab - go to next link in current menu list
e.preventDefault();
// if next link doesn't exist try next menu first item else return null
var nextLink = getMenuLink(linkIndex, thisSubMenuLinks, 'next') ? getMenuLink(linkIndex, thisSubMenuLinks, 'next') : getMenuLink(-1, allLinksArray[subMenuIndex + 1], 'next');
if (nextLink) {
nextLink.focus();
} else if (onLast) {
onLast();
}
} else if (e.keyCode === 38 || e.shiftKey && e.keyCode === 9) {
// up arrow or shift + tab - go to previous item in current menu list
e.preventDefault();
var prevMenu = allLinksArray[subMenuIndex - 1];
var prevLink = null;
if (prevMenu || linkIndex > 0) {
prevLink = getMenuLink(linkIndex, thisSubMenuLinks, 'prev') ? getMenuLink(linkIndex, thisSubMenuLinks, 'prev') : getMenuLink(prevMenu.length, prevMenu, 'prev');
}
if (prevLink) {
prevLink.focus();
} else if (onFirst) {
onFirst();
}
}
}
});
});
});
};
megaMenus.forEach(function(menu) {
var clearActiveListItems = function clearActiveListItems() {
// remove active classes from other list items
menu.querySelectorAll('.wmnds-mega-menu__primary-menu-item').forEach(function(menuItem) {
menuItem.classList.remove('active');
});
};
// handle setting the active class on menu and list items
var setMenuActive = function setMenuActive(element) {
var active = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
var onCloseFocusElement = arguments.length > 2 ? arguments[2] : undefined;
if (active) {
menu.classList.add('active');
clearActiveListItems();
// add active class to current item
element.classList.add('active');
} else {
menu.classList.remove('active');
element.classList.remove('active');
// set focus on menu close
if (onCloseFocusElement) onCloseFocusElement.focus();
}
};
// mobile nav function
function handleMobileMenu(mq) {
if (mq.matches) {
var mobileToggle = menu.querySelector('.wmnds-mega-menu__mobile-toggle');
var headerEl = menu.parentNode.parentNode;
var topLevelMenuBtn = menu.querySelectorAll('.wmnds-mega-menu__link-arrow-icon-btn');
var searchBtn = menu.querySelector('.wmnds-mega-menu__search-btn');
// object to see which menu/menu level is open
var mobileMenuIsOpen = {
menu: false,
primary: false,
search: false
};
// handle mobile menu toggle
mobileToggle.addEventListener('click', function() {
mobileMenuIsOpen.menu = !mobileMenuIsOpen.menu;
if (mobileMenuIsOpen.menu) {
mobileMenuIsOpen.search = false;
headerEl.classList.remove('wmnds-header--search-open');
headerEl.classList.add('wmnds-header--mega-menu-open');
document.querySelector('html').classList.add('mobile-menu-open');
} else {
headerEl.classList.remove('wmnds-header--mega-menu-open', 'wmnds-header--mega-menu-submenu-open');
document.querySelector('html').classList.remove('mobile-menu-open');
}
});
if (searchBtn) {
searchBtn.addEventListener('click', function() {
mobileMenuIsOpen.search = !mobileMenuIsOpen.search;
if (mobileMenuIsOpen.search) {
mobileMenuIsOpen.menu = false;
headerEl.classList.remove('wmnds-header--mega-menu-open', 'wmnds-header--mega-menu-submenu-open');
document.querySelector('html').classList.remove('mobile-menu-open');
headerEl.classList.add('wmnds-header--search-open');
} else {
headerEl.classList.remove('wmnds-header--search-open');
}
});
}
// handle sub menu open/close
topLevelMenuBtn.forEach(function(menuBtn) {
var handleSubMenus = function handleSubMenus() {
mobileMenuIsOpen.primary = !mobileMenuIsOpen.primary;
var targetListItem = menuBtn.parentNode;
if (mobileMenuIsOpen.primary) {
targetListItem.classList.add('open');
targetListItem.querySelector('.wmnds-mega-menu__sub-menu-link').focus();
headerEl.classList.add('wmnds-header--mega-menu-submenu-open');
} else {
targetListItem.classList.remove('open');
headerEl.classList.remove('wmnds-header--mega-menu-submenu-open');
}
};
menuBtn.previousElementSibling.addEventListener('click', handleSubMenus);
menuBtn.addEventListener('click', handleSubMenus);
});
// mobile collapse for third level menus
var collapseMenus = menu.querySelectorAll('.wmnds-mega-menu__sub-menu-item .wmnds-mega-menu__collapse-toggle');
collapseMenus.forEach(function(collapseToggle) {
var handleThirdLevelMenus = function handleThirdLevelMenus() {
var panel = collapseToggle.nextElementSibling;
collapseToggle.classList.toggle('open');
if (panel.style.maxHeight) {
panel.style.maxHeight = null;
} else {
panel.style.maxHeight = "".concat(panel.scrollHeight, "px");
}
};
if (collapseToggle.previousElementSibling.tagName !== 'A') {
collapseToggle.previousElementSibling.addEventListener('click', handleThirdLevelMenus);
}
collapseToggle.addEventListener('click', handleThirdLevelMenus);
});
}
}
// end mobile nav function
// init mobile nav function
handleMobileMenu(mobileMenu);
mobileMenu.addListener(handleMobileMenu);
var topLevelLinks = menu.querySelectorAll('.wmnds-mega-menu__primary-menu-link');
var menuDelay = false;
var enterTimeOut;
var leaveTimeOut;
var delayTime = 300;
// handle events within each top level list item
topLevelLinks.forEach(function(topLevelLink, topLevelLinkIndex) {
// return list item parent of the current link if it exists else return the link
var topLevelListItem = topLevelLink.parentNode.tagName === 'LI' || topLevelLink.parentNode.className.includes('wmnds-mega-menu__search') ? topLevelLink.parentNode : topLevelLink;
var subMenuLinks = topLevelListItem.querySelectorAll('.wmnds-mega-menu__sub-menu-link');
// check if level 3 menus are present, if so add modifier class
var hasSubmenuChildren = topLevelListItem.querySelectorAll('.wmnds-mega-menu__sub-menu-child-menu').length !== 0;
if (hasSubmenuChildren) {
topLevelListItem.querySelectorAll('.wmnds-mega-menu__sub-menu').forEach(function(subMenu) {
subMenu.classList.add('wmnds-mega-menu__sub-menu--has-child-menus');
});
}
var openSubMenu = function openSubMenu(e) {
// check if list item has a mega menu
if (topLevelListItem.querySelectorAll('.wmnds-mega-menu__container').length) {
e.preventDefault();
// remove keyFocus to allow menu to show
setMenuActive(topLevelListItem, true);
// focus first menu item
if (topLevelListItem.contains(subMenuLinks[0])) {
subMenuLinks[0].focus();
} else if (topLevelListItem.querySelector('.wmnds-search-bar__input')) {
topLevelListItem.querySelector('.wmnds-search-bar__input').focus();
}
}
};
var handleKeydown = function handleKeydown(e, key) {
e.stopPropagation();
// if key pressed is enter, space bar or down arrow
if (key === 13 || key === 32 || key === 40) {
// enter
// check if link exists
if (key === 13) {
if (!topLevelLink.tagName === 'a' || !topLevelLink.getAttribute('href')) {
openSubMenu(e);
}
} else {
openSubMenu(e);
}
} else if (key === 37) {
// left arrow
var prevLink = getMenuLink(topLevelLinkIndex, topLevelLinks, 'prev');
if (prevLink) prevLink.focus();
} else if (key === 39) {
// right arrow
var nextLink = getMenuLink(topLevelLinkIndex, topLevelLinks, 'next');
if (nextLink) nextLink.focus();
} else if (key === 27) {
// if escape pressed
setMenuActive(topLevelListItem, false, topLevelLink);
}
};
// if top level link doesn't have a mega-menu child add class to menu to hide overlay when hovered
// has to be added/removed on mouseover to cover menus that have a mix of items with/without mega menus
var isTopLevelWithMenu = topLevelListItem.querySelectorAll('.wmnds-mega-menu__container').length;
if (isTopLevelWithMenu) {
topLevelLink.addEventListener('mouseover', function() {
if (!menuDelay) {
// if no menuDelay is active just open the menu
setMenuActive(topLevelListItem);
} else {
// if menuDelay is active, clear all timeouts and start a new one
clearTimeout(enterTimeOut);
clearTimeout(leaveTimeOut);
enterTimeOut = setTimeout(function() {
// enter timeout completed, open menu and kill delay
menuDelay = false;
setMenuActive(topLevelListItem);
}, delayTime);
}
});
topLevelListItem.querySelector('.wmnds-mega-menu__container').addEventListener('mouseover', function() {
if (menuDelay) {
// if container is rehovered before timeout is done, clear all timeouts kill the delay
clearTimeout(enterTimeOut);
clearTimeout(leaveTimeOut);
menuDelay = false;
}
});
topLevelListItem.addEventListener('mouseleave', function() {
menuDelay = true;
// leave timeout is active
leaveTimeOut = setTimeout(function() {
// leave timeout completed, close menu
setMenuActive(topLevelListItem, false);
menuDelay = false;
}, delayTime);
});
topLevelListItem.addEventListener('blur', setMenuActive(topLevelListItem, false));
}
topLevelListItem.addEventListener('keydown', function(e) {
handleKeydown(e, e.keyCode);
});
// top lvl link event listeners
topLevelLink.addEventListener('focus', function(e) {
e.preventDefault();
setMenuActive(topLevelListItem, false);
clearActiveListItems();
});
topLevelLink.addEventListener('mousedown', function(e) {
// prevent link focus on click
e.preventDefault();
});
// set up keyboard navigation for sub menu links
var subMenuContainer = topLevelListItem.querySelector('.wmnds-mega-menu__sub-menu');
if (subMenuContainer) {
setKeyboardNavigation(subMenuContainer, '.wmnds-mega-menu__sub-menu-item',
// what to do on first link
function() {
return topLevelLink.focus();
},
// what to do on last link
function() {
setMenuActive(topLevelListItem, false);
if (getMenuLink(topLevelLinkIndex, topLevelLinks, 'next')) {
getMenuLink(topLevelLinkIndex, topLevelLinks, 'next').focus();
}
});
}
});
// set up keyboard navigation for search menu links
var searchMenuContainer = menu.querySelector('.wmnds-search-container');
if (searchMenuContainer) {
setKeyboardNavigation(searchMenuContainer, '.wmnds-search-list',
// what to do on first link
function() {
return menu.querySelector('.wmnds-search-bar__input').focus();
},
// what to do on last link
function() {
return setMenuActive(menu.querySelector('.wmnds-mega-menu__search'), false, menu.querySelector('.wmnds-mega-menu__search-btn'));
});
}
});
};
var _default = headerJs;
exports["default"] = _default;
Recommended javascript (ES6)
const headerJs = () => {
// get mega menu elements
const megaMenus = document.querySelectorAll('.wmnds-mega-menu');
const mobileMenu = window.matchMedia('(max-width: 767px)');
/*
Mega menu helper functions
*/
// getMenuLink returns a specified menu link from a specified array
// currentIndex = index of the link that is currently focused
// array = array to move through
// direction = next, prev,
const getMenuLink = (currentIndex, array, direction) => {
let menuLink = null;
if (array) {
if (direction === 'prev') {
// return previous link in specified array if there is one else return null;
menuLink = array[currentIndex - 1] ? array[currentIndex - 1] : null;
} else if (direction === 'next') {
// return next link in specified array if there is one else return null;
menuLink = array[currentIndex + 1] ? array[currentIndex + 1] : null;
} else {
// return link with same index in specified array;
menuLink = array[currentIndex] ? array[currentIndex] : array[array.length - 1];
}
}
return menuLink;
};
// takes a menu element and allows moving between focus via tabbing/arrows
const setKeyboardNavigation = (subMenuContainer, subMenuQuery, onFirst, onLast) => {
// array of all links in menu container
const allLinksArray = [];
// use specified query to select all submenus
const subMenus = subMenuContainer.querySelectorAll(subMenuQuery);
subMenus.forEach((subMenu, subMenuIndex) => {
const thisSubMenuLinks = subMenu.querySelectorAll('a');
// add list of all links in this container to an array
allLinksArray.push(thisSubMenuLinks);
// add event listener to each link with key logic
thisSubMenuLinks.forEach((link, linkIndex) => {
link.addEventListener('keydown', e => {
// if not escape
if (e.keyCode !== 27) {
e.stopPropagation();
if (e.keyCode === 39) {
// right arrow - go to link of same index in next menu list
e.preventDefault();
const nextMenuLink = getMenuLink(linkIndex, allLinksArray[subMenuIndex + 1]);
if (nextMenuLink) nextMenuLink.focus();
} else if (e.keyCode === 37) {
// left arrow - go to link of same index in previous menu list
e.preventDefault();
const prevMenuLink = getMenuLink(linkIndex, allLinksArray[subMenuIndex - 1]);
if (prevMenuLink) prevMenuLink.focus();
} else if (e.keyCode === 40 || (e.keyCode === 9 && !e.shiftKey)) {
// down arrow or tab - go to next link in current menu list
e.preventDefault();
// if next link doesn't exist try next menu first item else return null
const nextLink = getMenuLink(linkIndex, thisSubMenuLinks, 'next') ?
getMenuLink(linkIndex, thisSubMenuLinks, 'next') :
getMenuLink(-1, allLinksArray[subMenuIndex + 1], 'next');
if (nextLink) {
nextLink.focus();
} else if (onLast) {
onLast();
}
} else if (e.keyCode === 38 || (e.shiftKey && e.keyCode === 9)) {
// up arrow or shift + tab - go to previous item in current menu list
e.preventDefault();
const prevMenu = allLinksArray[subMenuIndex - 1];
let prevLink = null;
if (prevMenu || linkIndex > 0) {
prevLink = getMenuLink(linkIndex, thisSubMenuLinks, 'prev') ?
getMenuLink(linkIndex, thisSubMenuLinks, 'prev') :
getMenuLink(prevMenu.length, prevMenu, 'prev');
}
if (prevLink) {
prevLink.focus();
} else if (onFirst) {
onFirst();
}
}
}
});
});
});
};
megaMenus.forEach(menu => {
const clearActiveListItems = () => {
// remove active classes from other list items
menu.querySelectorAll('.wmnds-mega-menu__primary-menu-item').forEach(menuItem => {
menuItem.classList.remove('active');
});
};
// handle setting the active class on menu and list items
const setMenuActive = (element, active = true, onCloseFocusElement) => {
if (active) {
menu.classList.add('active');
clearActiveListItems();
// add active class to current item
element.classList.add('active');
} else {
menu.classList.remove('active');
element.classList.remove('active');
// set focus on menu close
if (onCloseFocusElement) onCloseFocusElement.focus();
}
};
// mobile nav function
function handleMobileMenu(mq) {
if (mq.matches) {
const mobileToggle = menu.querySelector('.wmnds-mega-menu__mobile-toggle');
const headerEl = menu.parentNode.parentNode;
const topLevelMenuBtn = menu.querySelectorAll('.wmnds-mega-menu__link-arrow-icon-btn');
const searchBtn = menu.querySelector('.wmnds-mega-menu__search-btn');
// object to see which menu/menu level is open
const mobileMenuIsOpen = {
menu: false,
primary: false,
search: false
};
// handle mobile menu toggle
mobileToggle.addEventListener('click', () => {
mobileMenuIsOpen.menu = !mobileMenuIsOpen.menu;
if (mobileMenuIsOpen.menu) {
mobileMenuIsOpen.search = false;
headerEl.classList.remove('wmnds-header--search-open');
headerEl.classList.add('wmnds-header--mega-menu-open');
document.querySelector('html').classList.add('mobile-menu-open');
} else {
headerEl.classList.remove(
'wmnds-header--mega-menu-open',
'wmnds-header--mega-menu-submenu-open'
);
document.querySelector('html').classList.remove('mobile-menu-open');
}
});
if (searchBtn) {
searchBtn.addEventListener('click', () => {
mobileMenuIsOpen.search = !mobileMenuIsOpen.search;
if (mobileMenuIsOpen.search) {
mobileMenuIsOpen.menu = false;
headerEl.classList.remove(
'wmnds-header--mega-menu-open',
'wmnds-header--mega-menu-submenu-open'
);
document.querySelector('html').classList.remove('mobile-menu-open');
headerEl.classList.add('wmnds-header--search-open');
} else {
headerEl.classList.remove('wmnds-header--search-open');
}
});
}
// handle sub menu open/close
topLevelMenuBtn.forEach(menuBtn => {
const handleSubMenus = () => {
mobileMenuIsOpen.primary = !mobileMenuIsOpen.primary;
const targetListItem = menuBtn.parentNode;
if (mobileMenuIsOpen.primary) {
targetListItem.classList.add('open');
targetListItem.querySelector('.wmnds-mega-menu__sub-menu-link').focus();
headerEl.classList.add('wmnds-header--mega-menu-submenu-open');
} else {
targetListItem.classList.remove('open');
headerEl.classList.remove('wmnds-header--mega-menu-submenu-open');
}
};
menuBtn.previousElementSibling.addEventListener('click', handleSubMenus);
menuBtn.addEventListener('click', handleSubMenus);
});
// mobile collapse for third level menus
const collapseMenus = menu.querySelectorAll(
'.wmnds-mega-menu__sub-menu-item .wmnds-mega-menu__collapse-toggle'
);
collapseMenus.forEach(collapseToggle => {
const handleThirdLevelMenus = () => {
const panel = collapseToggle.nextElementSibling;
collapseToggle.classList.toggle('open');
if (panel.style.maxHeight) {
panel.style.maxHeight = null;
} else {
panel.style.maxHeight = `${panel.scrollHeight}px`;
}
};
if (collapseToggle.previousElementSibling.tagName !== 'A') {
collapseToggle.previousElementSibling.addEventListener('click', handleThirdLevelMenus);
}
collapseToggle.addEventListener('click', handleThirdLevelMenus);
});
}
}
// end mobile nav function
// init mobile nav function
handleMobileMenu(mobileMenu);
mobileMenu.addListener(handleMobileMenu);
const topLevelLinks = menu.querySelectorAll('.wmnds-mega-menu__primary-menu-link');
let menuDelay = false;
let enterTimeOut;
let leaveTimeOut;
const delayTime = 300;
// handle events within each top level list item
topLevelLinks.forEach((topLevelLink, topLevelLinkIndex) => {
// return list item parent of the current link if it exists else return the link
const topLevelListItem =
topLevelLink.parentNode.tagName === 'LI' ||
topLevelLink.parentNode.className.includes('wmnds-mega-menu__search') ?
topLevelLink.parentNode :
topLevelLink;
const subMenuLinks = topLevelListItem.querySelectorAll('.wmnds-mega-menu__sub-menu-link');
// check if level 3 menus are present, if so add modifier class
const hasSubmenuChildren =
topLevelListItem.querySelectorAll('.wmnds-mega-menu__sub-menu-child-menu').length !== 0;
if (hasSubmenuChildren) {
topLevelListItem.querySelectorAll('.wmnds-mega-menu__sub-menu').forEach(subMenu => {
subMenu.classList.add('wmnds-mega-menu__sub-menu--has-child-menus');
});
}
const openSubMenu = e => {
// check if list item has a mega menu
if (topLevelListItem.querySelectorAll('.wmnds-mega-menu__container').length) {
e.preventDefault();
// remove keyFocus to allow menu to show
setMenuActive(topLevelListItem, true);
// focus first menu item
if (topLevelListItem.contains(subMenuLinks[0])) {
subMenuLinks[0].focus();
} else if (topLevelListItem.querySelector('.wmnds-search-bar__input')) {
topLevelListItem.querySelector('.wmnds-search-bar__input').focus();
}
}
};
const handleKeydown = (e, key) => {
e.stopPropagation();
// if key pressed is enter, space bar or down arrow
if (key === 13 || key === 32 || key === 40) {
// enter
// check if link exists
if (key === 13) {
if (!topLevelLink.tagName === 'a' || !topLevelLink.getAttribute('href')) {
openSubMenu(e);
}
} else {
openSubMenu(e);
}
} else if (key === 37) {
// left arrow
const prevLink = getMenuLink(topLevelLinkIndex, topLevelLinks, 'prev');
if (prevLink) prevLink.focus();
} else if (key === 39) {
// right arrow
const nextLink = getMenuLink(topLevelLinkIndex, topLevelLinks, 'next');
if (nextLink) nextLink.focus();
} else if (key === 27) {
// if escape pressed
setMenuActive(topLevelListItem, false, topLevelLink);
}
};
// if top level link doesn't have a mega-menu child add class to menu to hide overlay when hovered
// has to be added/removed on mouseover to cover menus that have a mix of items with/without mega menus
const isTopLevelWithMenu = topLevelListItem.querySelectorAll(
'.wmnds-mega-menu__container'
).length;
if (isTopLevelWithMenu) {
topLevelLink.addEventListener('mouseover', () => {
if (!menuDelay) {
// if no menuDelay is active just open the menu
setMenuActive(topLevelListItem);
} else {
// if menuDelay is active, clear all timeouts and start a new one
clearTimeout(enterTimeOut);
clearTimeout(leaveTimeOut);
enterTimeOut = setTimeout(() => {
// enter timeout completed, open menu and kill delay
menuDelay = false;
setMenuActive(topLevelListItem);
}, delayTime);
}
});
topLevelListItem
.querySelector('.wmnds-mega-menu__container')
.addEventListener('mouseover', () => {
if (menuDelay) {
// if container is rehovered before timeout is done, clear all timeouts kill the delay
clearTimeout(enterTimeOut);
clearTimeout(leaveTimeOut);
menuDelay = false;
}
});
topLevelListItem.addEventListener('mouseleave', () => {
menuDelay = true;
// leave timeout is active
leaveTimeOut = setTimeout(() => {
// leave timeout completed, close menu
setMenuActive(topLevelListItem, false);
menuDelay = false;
}, delayTime);
});
topLevelListItem.addEventListener('blur', setMenuActive(topLevelListItem, false));
}
topLevelListItem.addEventListener('keydown', e => {
handleKeydown(e, e.keyCode);
});
// top lvl link event listeners
topLevelLink.addEventListener('focus', e => {
e.preventDefault();
setMenuActive(topLevelListItem, false);
clearActiveListItems();
});
topLevelLink.addEventListener('mousedown', e => {
// prevent link focus on click
e.preventDefault();
});
// set up keyboard navigation for sub menu links
const subMenuContainer = topLevelListItem.querySelector('.wmnds-mega-menu__sub-menu');
if (subMenuContainer) {
setKeyboardNavigation(
subMenuContainer,
'.wmnds-mega-menu__sub-menu-item',
// what to do on first link
() => topLevelLink.focus(),
// what to do on last link
() => {
setMenuActive(topLevelListItem, false);
if (getMenuLink(topLevelLinkIndex, topLevelLinks, 'next')) {
getMenuLink(topLevelLinkIndex, topLevelLinks, 'next').focus();
}
}
);
}
});
// set up keyboard navigation for search menu links
const searchMenuContainer = menu.querySelector('.wmnds-search-container');
if (searchMenuContainer) {
setKeyboardNavigation(
searchMenuContainer,
'.wmnds-search-list',
// what to do on first link
() => menu.querySelector('.wmnds-search-bar__input').focus(),
// what to do on last link
() =>
setMenuActive(
menu.querySelector('.wmnds-mega-menu__search'),
false,
menu.querySelector('.wmnds-mega-menu__search-btn')
)
);
}
});
};
export default headerJs;
With a title variant of header
When developing a service you may not need the navigation of the main website. When this is the case, you can place the title of the service in the header (providing their is no other h1 element on the page).
To use the title variant:
- Add the h1 element and ensure it has the classes
.wmnds-header__title .wmnds-col-1 .wmnds-col-sm-auto
on the element
HTML markup
<!-- Skip to content link -->
<a href="#wmnds-main-content" title="Skip to main content" target="_self" class="wmnds-link wmnds-header__skip-link">
Skip to main content
</a>
<!-- Main header section -->
<header>
<div class="wmnds-bg-white wmnds-p-t-md wmnds-p-b-md wmnds-cookies-banner">
<div class="wmnds-container">
<div class="wmnds-col-1 wmnds-col-md-3-4 wmnds-col-lg-2-3">
<h3>Your privacy settings</h3>
<p>
We use cookies to help you with journey planning and relevant disruptions, remember your login and show you
content you might be interested in. If you’re happy with the use of cookies by West Midlands Combined Authority
and our selected partners, click ‘Accept all cookies’. Or click ‘Manage cookies’ to learn more.
</p>
<div class="wmnds-grid wmnds-grid--justify-between wmnds-cookies-banner__group-buttons">
<button class="wmnds-btn wmnds-col-1 wmnds-col-sm-1 wmnds-col-md-12-24 wmnds-cookies-banner__accept-all-cookies wmnds-text-align-center" type="button">
Accept all cookies
</button>
<a href="https://www.tfwm.org.uk/manage-cookies/" title="link title" target="_self" class="wmnds-btn wmnds-btn wmnds-col-1 wmnds-col-sm-1 wmnds-col-md-12-24 wmnds-text-align-center">
Manage Cookies
</a>
</div>
</div>
</div>
</div>
<div class="wmnds-header">
<div class="wmnds-container wmnds-grid wmnds-grid--align-center wmnds-grid--justify-between">
<div class="wmnds-header__vertical-align wmnds-col-auto">
<!-- Logo -->
<a class="wmnds-header__logo-link" href="/" title="Transport for West Midlands Design System">
<img class="wmnds-header__logo" alt="Transport for West Midlands logo" src="/img/logo.svg" />
</a>
</div>
<!-- Header title -->
<h1 class="wmnds-header__title wmnds-col-1 wmnds-col-sm-auto">
Reinstate your Direct Debit<br>(COVID-19)
</h1>
</div>
</div>
</header>
Recommended javascript (browser compatible)
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports["default"] = void 0;
var headerJs = function headerJs() {
// get mega menu elements
var megaMenus = document.querySelectorAll('.wmnds-mega-menu');
var mobileMenu = window.matchMedia('(max-width: 767px)');
/*
Mega menu helper functions
*/
// getMenuLink returns a specified menu link from a specified array
// currentIndex = index of the link that is currently focused
// array = array to move through
// direction = next, prev,
var getMenuLink = function getMenuLink(currentIndex, array, direction) {
var menuLink = null;
if (array) {
if (direction === 'prev') {
// return previous link in specified array if there is one else return null;
menuLink = array[currentIndex - 1] ? array[currentIndex - 1] : null;
} else if (direction === 'next') {
// return next link in specified array if there is one else return null;
menuLink = array[currentIndex + 1] ? array[currentIndex + 1] : null;
} else {
// return link with same index in specified array;
menuLink = array[currentIndex] ? array[currentIndex] : array[array.length - 1];
}
}
return menuLink;
};
// takes a menu element and allows moving between focus via tabbing/arrows
var setKeyboardNavigation = function setKeyboardNavigation(subMenuContainer, subMenuQuery, onFirst, onLast) {
// array of all links in menu container
var allLinksArray = [];
// use specified query to select all submenus
var subMenus = subMenuContainer.querySelectorAll(subMenuQuery);
subMenus.forEach(function(subMenu, subMenuIndex) {
var thisSubMenuLinks = subMenu.querySelectorAll('a');
// add list of all links in this container to an array
allLinksArray.push(thisSubMenuLinks);
// add event listener to each link with key logic
thisSubMenuLinks.forEach(function(link, linkIndex) {
link.addEventListener('keydown', function(e) {
// if not escape
if (e.keyCode !== 27) {
e.stopPropagation();
if (e.keyCode === 39) {
// right arrow - go to link of same index in next menu list
e.preventDefault();
var nextMenuLink = getMenuLink(linkIndex, allLinksArray[subMenuIndex + 1]);
if (nextMenuLink) nextMenuLink.focus();
} else if (e.keyCode === 37) {
// left arrow - go to link of same index in previous menu list
e.preventDefault();
var prevMenuLink = getMenuLink(linkIndex, allLinksArray[subMenuIndex - 1]);
if (prevMenuLink) prevMenuLink.focus();
} else if (e.keyCode === 40 || e.keyCode === 9 && !e.shiftKey) {
// down arrow or tab - go to next link in current menu list
e.preventDefault();
// if next link doesn't exist try next menu first item else return null
var nextLink = getMenuLink(linkIndex, thisSubMenuLinks, 'next') ? getMenuLink(linkIndex, thisSubMenuLinks, 'next') : getMenuLink(-1, allLinksArray[subMenuIndex + 1], 'next');
if (nextLink) {
nextLink.focus();
} else if (onLast) {
onLast();
}
} else if (e.keyCode === 38 || e.shiftKey && e.keyCode === 9) {
// up arrow or shift + tab - go to previous item in current menu list
e.preventDefault();
var prevMenu = allLinksArray[subMenuIndex - 1];
var prevLink = null;
if (prevMenu || linkIndex > 0) {
prevLink = getMenuLink(linkIndex, thisSubMenuLinks, 'prev') ? getMenuLink(linkIndex, thisSubMenuLinks, 'prev') : getMenuLink(prevMenu.length, prevMenu, 'prev');
}
if (prevLink) {
prevLink.focus();
} else if (onFirst) {
onFirst();
}
}
}
});
});
});
};
megaMenus.forEach(function(menu) {
var clearActiveListItems = function clearActiveListItems() {
// remove active classes from other list items
menu.querySelectorAll('.wmnds-mega-menu__primary-menu-item').forEach(function(menuItem) {
menuItem.classList.remove('active');
});
};
// handle setting the active class on menu and list items
var setMenuActive = function setMenuActive(element) {
var active = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
var onCloseFocusElement = arguments.length > 2 ? arguments[2] : undefined;
if (active) {
menu.classList.add('active');
clearActiveListItems();
// add active class to current item
element.classList.add('active');
} else {
menu.classList.remove('active');
element.classList.remove('active');
// set focus on menu close
if (onCloseFocusElement) onCloseFocusElement.focus();
}
};
// mobile nav function
function handleMobileMenu(mq) {
if (mq.matches) {
var mobileToggle = menu.querySelector('.wmnds-mega-menu__mobile-toggle');
var headerEl = menu.parentNode.parentNode;
var topLevelMenuBtn = menu.querySelectorAll('.wmnds-mega-menu__link-arrow-icon-btn');
var searchBtn = menu.querySelector('.wmnds-mega-menu__search-btn');
// object to see which menu/menu level is open
var mobileMenuIsOpen = {
menu: false,
primary: false,
search: false
};
// handle mobile menu toggle
mobileToggle.addEventListener('click', function() {
mobileMenuIsOpen.menu = !mobileMenuIsOpen.menu;
if (mobileMenuIsOpen.menu) {
mobileMenuIsOpen.search = false;
headerEl.classList.remove('wmnds-header--search-open');
headerEl.classList.add('wmnds-header--mega-menu-open');
document.querySelector('html').classList.add('mobile-menu-open');
} else {
headerEl.classList.remove('wmnds-header--mega-menu-open', 'wmnds-header--mega-menu-submenu-open');
document.querySelector('html').classList.remove('mobile-menu-open');
}
});
if (searchBtn) {
searchBtn.addEventListener('click', function() {
mobileMenuIsOpen.search = !mobileMenuIsOpen.search;
if (mobileMenuIsOpen.search) {
mobileMenuIsOpen.menu = false;
headerEl.classList.remove('wmnds-header--mega-menu-open', 'wmnds-header--mega-menu-submenu-open');
document.querySelector('html').classList.remove('mobile-menu-open');
headerEl.classList.add('wmnds-header--search-open');
} else {
headerEl.classList.remove('wmnds-header--search-open');
}
});
}
// handle sub menu open/close
topLevelMenuBtn.forEach(function(menuBtn) {
var handleSubMenus = function handleSubMenus() {
mobileMenuIsOpen.primary = !mobileMenuIsOpen.primary;
var targetListItem = menuBtn.parentNode;
if (mobileMenuIsOpen.primary) {
targetListItem.classList.add('open');
targetListItem.querySelector('.wmnds-mega-menu__sub-menu-link').focus();
headerEl.classList.add('wmnds-header--mega-menu-submenu-open');
} else {
targetListItem.classList.remove('open');
headerEl.classList.remove('wmnds-header--mega-menu-submenu-open');
}
};
menuBtn.previousElementSibling.addEventListener('click', handleSubMenus);
menuBtn.addEventListener('click', handleSubMenus);
});
// mobile collapse for third level menus
var collapseMenus = menu.querySelectorAll('.wmnds-mega-menu__sub-menu-item .wmnds-mega-menu__collapse-toggle');
collapseMenus.forEach(function(collapseToggle) {
var handleThirdLevelMenus = function handleThirdLevelMenus() {
var panel = collapseToggle.nextElementSibling;
collapseToggle.classList.toggle('open');
if (panel.style.maxHeight) {
panel.style.maxHeight = null;
} else {
panel.style.maxHeight = "".concat(panel.scrollHeight, "px");
}
};
if (collapseToggle.previousElementSibling.tagName !== 'A') {
collapseToggle.previousElementSibling.addEventListener('click', handleThirdLevelMenus);
}
collapseToggle.addEventListener('click', handleThirdLevelMenus);
});
}
}
// end mobile nav function
// init mobile nav function
handleMobileMenu(mobileMenu);
mobileMenu.addListener(handleMobileMenu);
var topLevelLinks = menu.querySelectorAll('.wmnds-mega-menu__primary-menu-link');
var menuDelay = false;
var enterTimeOut;
var leaveTimeOut;
var delayTime = 300;
// handle events within each top level list item
topLevelLinks.forEach(function(topLevelLink, topLevelLinkIndex) {
// return list item parent of the current link if it exists else return the link
var topLevelListItem = topLevelLink.parentNode.tagName === 'LI' || topLevelLink.parentNode.className.includes('wmnds-mega-menu__search') ? topLevelLink.parentNode : topLevelLink;
var subMenuLinks = topLevelListItem.querySelectorAll('.wmnds-mega-menu__sub-menu-link');
// check if level 3 menus are present, if so add modifier class
var hasSubmenuChildren = topLevelListItem.querySelectorAll('.wmnds-mega-menu__sub-menu-child-menu').length !== 0;
if (hasSubmenuChildren) {
topLevelListItem.querySelectorAll('.wmnds-mega-menu__sub-menu').forEach(function(subMenu) {
subMenu.classList.add('wmnds-mega-menu__sub-menu--has-child-menus');
});
}
var openSubMenu = function openSubMenu(e) {
// check if list item has a mega menu
if (topLevelListItem.querySelectorAll('.wmnds-mega-menu__container').length) {
e.preventDefault();
// remove keyFocus to allow menu to show
setMenuActive(topLevelListItem, true);
// focus first menu item
if (topLevelListItem.contains(subMenuLinks[0])) {
subMenuLinks[0].focus();
} else if (topLevelListItem.querySelector('.wmnds-search-bar__input')) {
topLevelListItem.querySelector('.wmnds-search-bar__input').focus();
}
}
};
var handleKeydown = function handleKeydown(e, key) {
e.stopPropagation();
// if key pressed is enter, space bar or down arrow
if (key === 13 || key === 32 || key === 40) {
// enter
// check if link exists
if (key === 13) {
if (!topLevelLink.tagName === 'a' || !topLevelLink.getAttribute('href')) {
openSubMenu(e);
}
} else {
openSubMenu(e);
}
} else if (key === 37) {
// left arrow
var prevLink = getMenuLink(topLevelLinkIndex, topLevelLinks, 'prev');
if (prevLink) prevLink.focus();
} else if (key === 39) {
// right arrow
var nextLink = getMenuLink(topLevelLinkIndex, topLevelLinks, 'next');
if (nextLink) nextLink.focus();
} else if (key === 27) {
// if escape pressed
setMenuActive(topLevelListItem, false, topLevelLink);
}
};
// if top level link doesn't have a mega-menu child add class to menu to hide overlay when hovered
// has to be added/removed on mouseover to cover menus that have a mix of items with/without mega menus
var isTopLevelWithMenu = topLevelListItem.querySelectorAll('.wmnds-mega-menu__container').length;
if (isTopLevelWithMenu) {
topLevelLink.addEventListener('mouseover', function() {
if (!menuDelay) {
// if no menuDelay is active just open the menu
setMenuActive(topLevelListItem);
} else {
// if menuDelay is active, clear all timeouts and start a new one
clearTimeout(enterTimeOut);
clearTimeout(leaveTimeOut);
enterTimeOut = setTimeout(function() {
// enter timeout completed, open menu and kill delay
menuDelay = false;
setMenuActive(topLevelListItem);
}, delayTime);
}
});
topLevelListItem.querySelector('.wmnds-mega-menu__container').addEventListener('mouseover', function() {
if (menuDelay) {
// if container is rehovered before timeout is done, clear all timeouts kill the delay
clearTimeout(enterTimeOut);
clearTimeout(leaveTimeOut);
menuDelay = false;
}
});
topLevelListItem.addEventListener('mouseleave', function() {
menuDelay = true;
// leave timeout is active
leaveTimeOut = setTimeout(function() {
// leave timeout completed, close menu
setMenuActive(topLevelListItem, false);
menuDelay = false;
}, delayTime);
});
topLevelListItem.addEventListener('blur', setMenuActive(topLevelListItem, false));
}
topLevelListItem.addEventListener('keydown', function(e) {
handleKeydown(e, e.keyCode);
});
// top lvl link event listeners
topLevelLink.addEventListener('focus', function(e) {
e.preventDefault();
setMenuActive(topLevelListItem, false);
clearActiveListItems();
});
topLevelLink.addEventListener('mousedown', function(e) {
// prevent link focus on click
e.preventDefault();
});
// set up keyboard navigation for sub menu links
var subMenuContainer = topLevelListItem.querySelector('.wmnds-mega-menu__sub-menu');
if (subMenuContainer) {
setKeyboardNavigation(subMenuContainer, '.wmnds-mega-menu__sub-menu-item',
// what to do on first link
function() {
return topLevelLink.focus();
},
// what to do on last link
function() {
setMenuActive(topLevelListItem, false);
if (getMenuLink(topLevelLinkIndex, topLevelLinks, 'next')) {
getMenuLink(topLevelLinkIndex, topLevelLinks, 'next').focus();
}
});
}
});
// set up keyboard navigation for search menu links
var searchMenuContainer = menu.querySelector('.wmnds-search-container');
if (searchMenuContainer) {
setKeyboardNavigation(searchMenuContainer, '.wmnds-search-list',
// what to do on first link
function() {
return menu.querySelector('.wmnds-search-bar__input').focus();
},
// what to do on last link
function() {
return setMenuActive(menu.querySelector('.wmnds-mega-menu__search'), false, menu.querySelector('.wmnds-mega-menu__search-btn'));
});
}
});
};
var _default = headerJs;
exports["default"] = _default;
Recommended javascript (ES6)
const headerJs = () => {
// get mega menu elements
const megaMenus = document.querySelectorAll('.wmnds-mega-menu');
const mobileMenu = window.matchMedia('(max-width: 767px)');
/*
Mega menu helper functions
*/
// getMenuLink returns a specified menu link from a specified array
// currentIndex = index of the link that is currently focused
// array = array to move through
// direction = next, prev,
const getMenuLink = (currentIndex, array, direction) => {
let menuLink = null;
if (array) {
if (direction === 'prev') {
// return previous link in specified array if there is one else return null;
menuLink = array[currentIndex - 1] ? array[currentIndex - 1] : null;
} else if (direction === 'next') {
// return next link in specified array if there is one else return null;
menuLink = array[currentIndex + 1] ? array[currentIndex + 1] : null;
} else {
// return link with same index in specified array;
menuLink = array[currentIndex] ? array[currentIndex] : array[array.length - 1];
}
}
return menuLink;
};
// takes a menu element and allows moving between focus via tabbing/arrows
const setKeyboardNavigation = (subMenuContainer, subMenuQuery, onFirst, onLast) => {
// array of all links in menu container
const allLinksArray = [];
// use specified query to select all submenus
const subMenus = subMenuContainer.querySelectorAll(subMenuQuery);
subMenus.forEach((subMenu, subMenuIndex) => {
const thisSubMenuLinks = subMenu.querySelectorAll('a');
// add list of all links in this container to an array
allLinksArray.push(thisSubMenuLinks);
// add event listener to each link with key logic
thisSubMenuLinks.forEach((link, linkIndex) => {
link.addEventListener('keydown', e => {
// if not escape
if (e.keyCode !== 27) {
e.stopPropagation();
if (e.keyCode === 39) {
// right arrow - go to link of same index in next menu list
e.preventDefault();
const nextMenuLink = getMenuLink(linkIndex, allLinksArray[subMenuIndex + 1]);
if (nextMenuLink) nextMenuLink.focus();
} else if (e.keyCode === 37) {
// left arrow - go to link of same index in previous menu list
e.preventDefault();
const prevMenuLink = getMenuLink(linkIndex, allLinksArray[subMenuIndex - 1]);
if (prevMenuLink) prevMenuLink.focus();
} else if (e.keyCode === 40 || (e.keyCode === 9 && !e.shiftKey)) {
// down arrow or tab - go to next link in current menu list
e.preventDefault();
// if next link doesn't exist try next menu first item else return null
const nextLink = getMenuLink(linkIndex, thisSubMenuLinks, 'next') ?
getMenuLink(linkIndex, thisSubMenuLinks, 'next') :
getMenuLink(-1, allLinksArray[subMenuIndex + 1], 'next');
if (nextLink) {
nextLink.focus();
} else if (onLast) {
onLast();
}
} else if (e.keyCode === 38 || (e.shiftKey && e.keyCode === 9)) {
// up arrow or shift + tab - go to previous item in current menu list
e.preventDefault();
const prevMenu = allLinksArray[subMenuIndex - 1];
let prevLink = null;
if (prevMenu || linkIndex > 0) {
prevLink = getMenuLink(linkIndex, thisSubMenuLinks, 'prev') ?
getMenuLink(linkIndex, thisSubMenuLinks, 'prev') :
getMenuLink(prevMenu.length, prevMenu, 'prev');
}
if (prevLink) {
prevLink.focus();
} else if (onFirst) {
onFirst();
}
}
}
});
});
});
};
megaMenus.forEach(menu => {
const clearActiveListItems = () => {
// remove active classes from other list items
menu.querySelectorAll('.wmnds-mega-menu__primary-menu-item').forEach(menuItem => {
menuItem.classList.remove('active');
});
};
// handle setting the active class on menu and list items
const setMenuActive = (element, active = true, onCloseFocusElement) => {
if (active) {
menu.classList.add('active');
clearActiveListItems();
// add active class to current item
element.classList.add('active');
} else {
menu.classList.remove('active');
element.classList.remove('active');
// set focus on menu close
if (onCloseFocusElement) onCloseFocusElement.focus();
}
};
// mobile nav function
function handleMobileMenu(mq) {
if (mq.matches) {
const mobileToggle = menu.querySelector('.wmnds-mega-menu__mobile-toggle');
const headerEl = menu.parentNode.parentNode;
const topLevelMenuBtn = menu.querySelectorAll('.wmnds-mega-menu__link-arrow-icon-btn');
const searchBtn = menu.querySelector('.wmnds-mega-menu__search-btn');
// object to see which menu/menu level is open
const mobileMenuIsOpen = {
menu: false,
primary: false,
search: false
};
// handle mobile menu toggle
mobileToggle.addEventListener('click', () => {
mobileMenuIsOpen.menu = !mobileMenuIsOpen.menu;
if (mobileMenuIsOpen.menu) {
mobileMenuIsOpen.search = false;
headerEl.classList.remove('wmnds-header--search-open');
headerEl.classList.add('wmnds-header--mega-menu-open');
document.querySelector('html').classList.add('mobile-menu-open');
} else {
headerEl.classList.remove(
'wmnds-header--mega-menu-open',
'wmnds-header--mega-menu-submenu-open'
);
document.querySelector('html').classList.remove('mobile-menu-open');
}
});
if (searchBtn) {
searchBtn.addEventListener('click', () => {
mobileMenuIsOpen.search = !mobileMenuIsOpen.search;
if (mobileMenuIsOpen.search) {
mobileMenuIsOpen.menu = false;
headerEl.classList.remove(
'wmnds-header--mega-menu-open',
'wmnds-header--mega-menu-submenu-open'
);
document.querySelector('html').classList.remove('mobile-menu-open');
headerEl.classList.add('wmnds-header--search-open');
} else {
headerEl.classList.remove('wmnds-header--search-open');
}
});
}
// handle sub menu open/close
topLevelMenuBtn.forEach(menuBtn => {
const handleSubMenus = () => {
mobileMenuIsOpen.primary = !mobileMenuIsOpen.primary;
const targetListItem = menuBtn.parentNode;
if (mobileMenuIsOpen.primary) {
targetListItem.classList.add('open');
targetListItem.querySelector('.wmnds-mega-menu__sub-menu-link').focus();
headerEl.classList.add('wmnds-header--mega-menu-submenu-open');
} else {
targetListItem.classList.remove('open');
headerEl.classList.remove('wmnds-header--mega-menu-submenu-open');
}
};
menuBtn.previousElementSibling.addEventListener('click', handleSubMenus);
menuBtn.addEventListener('click', handleSubMenus);
});
// mobile collapse for third level menus
const collapseMenus = menu.querySelectorAll(
'.wmnds-mega-menu__sub-menu-item .wmnds-mega-menu__collapse-toggle'
);
collapseMenus.forEach(collapseToggle => {
const handleThirdLevelMenus = () => {
const panel = collapseToggle.nextElementSibling;
collapseToggle.classList.toggle('open');
if (panel.style.maxHeight) {
panel.style.maxHeight = null;
} else {
panel.style.maxHeight = `${panel.scrollHeight}px`;
}
};
if (collapseToggle.previousElementSibling.tagName !== 'A') {
collapseToggle.previousElementSibling.addEventListener('click', handleThirdLevelMenus);
}
collapseToggle.addEventListener('click', handleThirdLevelMenus);
});
}
}
// end mobile nav function
// init mobile nav function
handleMobileMenu(mobileMenu);
mobileMenu.addListener(handleMobileMenu);
const topLevelLinks = menu.querySelectorAll('.wmnds-mega-menu__primary-menu-link');
let menuDelay = false;
let enterTimeOut;
let leaveTimeOut;
const delayTime = 300;
// handle events within each top level list item
topLevelLinks.forEach((topLevelLink, topLevelLinkIndex) => {
// return list item parent of the current link if it exists else return the link
const topLevelListItem =
topLevelLink.parentNode.tagName === 'LI' ||
topLevelLink.parentNode.className.includes('wmnds-mega-menu__search') ?
topLevelLink.parentNode :
topLevelLink;
const subMenuLinks = topLevelListItem.querySelectorAll('.wmnds-mega-menu__sub-menu-link');
// check if level 3 menus are present, if so add modifier class
const hasSubmenuChildren =
topLevelListItem.querySelectorAll('.wmnds-mega-menu__sub-menu-child-menu').length !== 0;
if (hasSubmenuChildren) {
topLevelListItem.querySelectorAll('.wmnds-mega-menu__sub-menu').forEach(subMenu => {
subMenu.classList.add('wmnds-mega-menu__sub-menu--has-child-menus');
});
}
const openSubMenu = e => {
// check if list item has a mega menu
if (topLevelListItem.querySelectorAll('.wmnds-mega-menu__container').length) {
e.preventDefault();
// remove keyFocus to allow menu to show
setMenuActive(topLevelListItem, true);
// focus first menu item
if (topLevelListItem.contains(subMenuLinks[0])) {
subMenuLinks[0].focus();
} else if (topLevelListItem.querySelector('.wmnds-search-bar__input')) {
topLevelListItem.querySelector('.wmnds-search-bar__input').focus();
}
}
};
const handleKeydown = (e, key) => {
e.stopPropagation();
// if key pressed is enter, space bar or down arrow
if (key === 13 || key === 32 || key === 40) {
// enter
// check if link exists
if (key === 13) {
if (!topLevelLink.tagName === 'a' || !topLevelLink.getAttribute('href')) {
openSubMenu(e);
}
} else {
openSubMenu(e);
}
} else if (key === 37) {
// left arrow
const prevLink = getMenuLink(topLevelLinkIndex, topLevelLinks, 'prev');
if (prevLink) prevLink.focus();
} else if (key === 39) {
// right arrow
const nextLink = getMenuLink(topLevelLinkIndex, topLevelLinks, 'next');
if (nextLink) nextLink.focus();
} else if (key === 27) {
// if escape pressed
setMenuActive(topLevelListItem, false, topLevelLink);
}
};
// if top level link doesn't have a mega-menu child add class to menu to hide overlay when hovered
// has to be added/removed on mouseover to cover menus that have a mix of items with/without mega menus
const isTopLevelWithMenu = topLevelListItem.querySelectorAll(
'.wmnds-mega-menu__container'
).length;
if (isTopLevelWithMenu) {
topLevelLink.addEventListener('mouseover', () => {
if (!menuDelay) {
// if no menuDelay is active just open the menu
setMenuActive(topLevelListItem);
} else {
// if menuDelay is active, clear all timeouts and start a new one
clearTimeout(enterTimeOut);
clearTimeout(leaveTimeOut);
enterTimeOut = setTimeout(() => {
// enter timeout completed, open menu and kill delay
menuDelay = false;
setMenuActive(topLevelListItem);
}, delayTime);
}
});
topLevelListItem
.querySelector('.wmnds-mega-menu__container')
.addEventListener('mouseover', () => {
if (menuDelay) {
// if container is rehovered before timeout is done, clear all timeouts kill the delay
clearTimeout(enterTimeOut);
clearTimeout(leaveTimeOut);
menuDelay = false;
}
});
topLevelListItem.addEventListener('mouseleave', () => {
menuDelay = true;
// leave timeout is active
leaveTimeOut = setTimeout(() => {
// leave timeout completed, close menu
setMenuActive(topLevelListItem, false);
menuDelay = false;
}, delayTime);
});
topLevelListItem.addEventListener('blur', setMenuActive(topLevelListItem, false));
}
topLevelListItem.addEventListener('keydown', e => {
handleKeydown(e, e.keyCode);
});
// top lvl link event listeners
topLevelLink.addEventListener('focus', e => {
e.preventDefault();
setMenuActive(topLevelListItem, false);
clearActiveListItems();
});
topLevelLink.addEventListener('mousedown', e => {
// prevent link focus on click
e.preventDefault();
});
// set up keyboard navigation for sub menu links
const subMenuContainer = topLevelListItem.querySelector('.wmnds-mega-menu__sub-menu');
if (subMenuContainer) {
setKeyboardNavigation(
subMenuContainer,
'.wmnds-mega-menu__sub-menu-item',
// what to do on first link
() => topLevelLink.focus(),
// what to do on last link
() => {
setMenuActive(topLevelListItem, false);
if (getMenuLink(topLevelLinkIndex, topLevelLinks, 'next')) {
getMenuLink(topLevelLinkIndex, topLevelLinks, 'next').focus();
}
}
);
}
});
// set up keyboard navigation for search menu links
const searchMenuContainer = menu.querySelector('.wmnds-search-container');
if (searchMenuContainer) {
setKeyboardNavigation(
searchMenuContainer,
'.wmnds-search-list',
// what to do on first link
() => menu.querySelector('.wmnds-search-bar__input').focus(),
// what to do on last link
() =>
setMenuActive(
menu.querySelector('.wmnds-mega-menu__search'),
false,
menu.querySelector('.wmnds-mega-menu__search-btn')
)
);
}
});
};
export default headerJs;
Archived header
This is an old version fo the header pattern for reference only. Use the current version instead.
HTML markup
<!-- Skip to content link -->
<a href="#wmnds-main-content" title="Skip to main content" target="_self" class="wmnds-link wmnds-header__skip-link">
Skip to main content
</a>
<!-- Main header section -->
<header>
<div class="wmnds-bg-white wmnds-p-t-md wmnds-p-b-md wmnds-cookies-banner">
<div class="wmnds-container">
<div class="wmnds-col-1 wmnds-col-md-3-4 wmnds-col-lg-2-3">
<h3>Your privacy settings</h3>
<p>
We use cookies to help you with journey planning and relevant disruptions, remember your login and show you
content you might be interested in. If you’re happy with the use of cookies by West Midlands Combined Authority
and our selected partners, click ‘Accept all cookies’. Or click ‘Manage cookies’ to learn more.
</p>
<div class="wmnds-grid wmnds-grid--justify-between wmnds-cookies-banner__group-buttons">
<button class="wmnds-btn wmnds-col-1 wmnds-col-sm-1 wmnds-col-md-12-24 wmnds-cookies-banner__accept-all-cookies wmnds-text-align-center" type="button">
Accept all cookies
</button>
<a href="https://www.tfwm.org.uk/manage-cookies/" title="link title" target="_self" class="wmnds-btn wmnds-btn wmnds-col-1 wmnds-col-sm-1 wmnds-col-md-12-24 wmnds-text-align-center">
Manage Cookies
</a>
</div>
</div>
</div>
</div>
<div class="wmnds-header">
<div class="wmnds-container wmnds-grid wmnds-grid--align-center wmnds-grid--justify-between">
<div class="wmnds-header__vertical-align wmnds-col-auto">
<!-- Logo -->
<a class="wmnds-header__logo-link" href="/" title="Transport for West Midlands Design System">
<img class="wmnds-header__logo" alt="Transport for West Midlands logo" src="/img/logo.svg" />
</a>
</div>
<!-- Nav items -->
<nav class="wmnds-header__vertical-align wmnds-col-auto" aria-label="Full header example navigation">
<ul class="wmnds-header__links wmnds-header__main-links">
<li class="wmnds-header__link">
<a href="/docs/" title="Docs page" target="_self" class="wmnds-link">
Docs
</a>
</li>
<li class="wmnds-header__link">
<a href="/styles/" title="Styles page" target="_self" class="wmnds-link">
Styles
</a>
</li>
<li class="wmnds-header__link">
<a href="/components/" title="Components page" target="_self" class="wmnds-link">
Components
</a>
</li>
<li class="wmnds-header__link">
<a href="/patterns/" title="Patterns page" target="_self" class="wmnds-link">
Patterns
</a>
</li>
<li class="wmnds-header__link">
<a href="/user-research/" title="User research page" target="_self" class="wmnds-link">
User research
</a>
</li>
</ul>
</nav>
</div>
</div>
<div class="wmnds-container">
<!-- Phase banner -->
<div class="wmnds-banner-container">
<div class="wmnds-col-1">
<div class="wmnds-banner-container__phase-wrapper">
<span class="wmnds-phase-indicator">
Beta
</span>
</div>
<p class="wmnds-banner-container__text">
This is a new service - your
<a href="https://github.com/wmcadigital/wmn-design-system/issues" title="WMN Design System Github" target="_blank" class="wmnds-link" rel="noopener noreferrer">
feedback
</a>
will help us to improve it.
</p>
</div>
</div>
<!-- Breadcrumbs -->
<nav aria-label="Breadcrumbs" class="wmnds-breadcrumb">
<ol class="wmnds-breadcrumb__list">
<li class="wmnds-breadcrumb__list-item">
<a href="/" class="wmnds-breadcrumb__link">
Home
</a>
</li>
<li class="wmnds-breadcrumb__list-item">
<a href="/patterns" class="wmnds-breadcrumb__link">
Patterns
</a>
</li>
<li class="wmnds-breadcrumb__list-item">
<a href="/patterns/header" class="wmnds-breadcrumb__link wmnds-breadcrumb__link--current" aria-current="page">
Header
</a>
</li>
</ol>
</nav>
</div>
</header>
Mobile app variant of header
When developing a mobile app, if you are limited for space and need extra room then it is recommended to use the mobile app variant of the Transport for West Midlands header.
To use the mobile app variant:
- Add the modifier class
wmnds-header--mobile-app
to the wmnds-header element - Copy the code for the back button section below and insert it into your existing header
- Finally, add the script for the back button in your local javascript file
You will most likely want to use this with the mobile variants of the breadcrumb component and footer pattern .
HTML markup
<!-- Skip to content link -->
<a href="#wmnds-main-content" title="Skip to main content" target="_self" class="wmnds-link wmnds-header__skip-link">
Skip to main content
</a>
<!-- Main header section -->
<header>
<div class="wmnds-bg-white wmnds-p-t-md wmnds-p-b-md wmnds-cookies-banner">
<div class="wmnds-container">
<div class="wmnds-col-1 wmnds-col-md-3-4 wmnds-col-lg-2-3">
<h3>Your privacy settings</h3>
<p>
We use cookies to help you with journey planning and relevant disruptions, remember your login and show you
content you might be interested in. If you’re happy with the use of cookies by West Midlands Combined Authority
and our selected partners, click ‘Accept all cookies’. Or click ‘Manage cookies’ to learn more.
</p>
<div class="wmnds-grid wmnds-grid--justify-between wmnds-cookies-banner__group-buttons">
<button class="wmnds-btn wmnds-col-1 wmnds-col-sm-1 wmnds-col-md-12-24 wmnds-cookies-banner__accept-all-cookies wmnds-text-align-center" type="button">
Accept all cookies
</button>
<a href="https://www.tfwm.org.uk/manage-cookies/" title="link title" target="_self" class="wmnds-btn wmnds-btn wmnds-col-1 wmnds-col-sm-1 wmnds-col-md-12-24 wmnds-text-align-center">
Manage Cookies
</a>
</div>
</div>
</div>
</div>
<div class="wmnds-header wmnds-header--mobile-app">
<div class="wmnds-container wmnds-grid wmnds-grid--align-center wmnds-grid--justify-between">
<div class="wmnds-header__vertical-align wmnds-col-auto">
<!-- Logo -->
<a class="wmnds-header__logo-link" href="/" title="Transport for West Midlands Design System">
<img class="wmnds-header__logo" alt="Transport for West Midlands logo" src="/img/logo.svg" />
</a>
</div>
<!-- Nav items -->
<nav class="wmnds-header__vertical-align wmnds-col-auto" aria-label="Mobile app header example navigation">
<ul class="wmnds-header__links wmnds-header__main-links">
<li class="wmnds-header__link">
<a href="/docs/" title="Docs page" target="_self" class="wmnds-link">
Docs
</a>
</li>
<li class="wmnds-header__link">
<a href="/styles/" title="Styles page" target="_self" class="wmnds-link">
Styles
</a>
</li>
<li class="wmnds-header__link">
<a href="/components/" title="Components page" target="_self" class="wmnds-link">
Components
</a>
</li>
<li class="wmnds-header__link">
<a href="/patterns/" title="Patterns page" target="_self" class="wmnds-link">
Patterns
</a>
</li>
<li class="wmnds-header__link">
<a href="/user-research/" title="User research page" target="_self" class="wmnds-link">
User research
</a>
</li>
</ul>
<!-- Back button -->
<ul class="wmnds-header__links wmnds-header__back-btn">
<li class="wmnds-header__link">
<a href="#" title="Go back to previous page" target="_self" class="wmnds-link" onClick="goBack(event);">
Back
</a>
</li>
</ul>
<script>
// Function that sends user to previous page they were on,
// This can be included in your local javascript file
function goBack(e) {
e.preventDefault();
window.history.back();
}
</script>
<!-- End back button -->
</nav>
</div>
</div>
</header>