Menu

Main menu

The main menu component helps users understand what the main sections of the website are, what the website is about and what they can find or do on the website.

When to use this component

Use the main menu component to offer one-click access to top tasks and/or the main sections of the website and to help users understand what the website is about and what they can find or do on the website.

A main menu is required for most websites.

When a menu menu is used, the main menu should be included:

  • On all pages of the website.
  • On all resolutions (desktop, tablet and mobile).

When not to use this component

Do not use the main menu component on:

  1. Websites that only have 1 main section. In this case, a main menu is not unnecessary and should not be used.
  2. Websites that have a lot of main sections and where the number of sections cannot be reduced even with an optimized information architecture. In this case a main menu is not the best choice. For this kind of websites, it’s better to rely on overview pages.

How it works

A main menu is composed as follows:

  1. A set of links to top tasks and/or the main sections of the website.
  2. The first item is a link to the front page of the website. In most cases, this is the page called the home page. This is shown using the label “Home”. In some special cases, when the front page is not a general home page, but has a more specific goal or specific content, the label can be something else than “Home”. However, this is an exception.
  3. Each item/link can be selected/active to show to website users on what page or in what main section of the website they are. Only one link at a time can be selected/active.

Javascript

This is a Javascript enabled molecule. Please check the javascript documentation on how to implement this correctly in your project.

<nav class="menu">
  <button class="toggle">{{ 'Menu' }}</button>
  <div class="overlay"></div>
  <div class="drawer" tabindex="-1">
    <div class="header">
      {% include '@logo' %}
      <button class="close">{{ 'Close menu' }}</button>
    </div>
    {% include '@list' with {
      type: 'links',
      items: items
    } %}
  </div>
</nav>
<nav class="menu">
    <button class="toggle">Menu</button>
    <div class="overlay"></div>
    <div class="drawer" tabindex="-1">
        <div class="header">
            <a href="#" title="Home" class="site-logo " rel="home">

            </a>
            <button class="close">Close menu</button>
        </div>

        <ul class="links ">
            <li><a href="#" class="active">Home</a></li>
            <li><a href="#">Menu item 1</a></li>
            <li><a href="#">Menu item 2</a></li>
            <li><a href="https://www.google.com">Google</a></li>
        </ul>

    </div>
</nav>
{
  "items": [
    "<a href=\"#\" class=\"active\">Home</a>",
    "<a href=\"#\">Menu item 1</a>",
    "<a href=\"#\">Menu item 2</a>",
    "<a href=\"https://www.google.com\">Google</a>"
  ]
}
  • Content:
    nav.menu {
      margin: 0;
    
      @include tablet {
        flex-basis: 100%;
        flex-grow: 1;
        order: 9999;
      }
    
      .drawer {
        position: fixed;
        top: 0;
        left: calc(-100% - 16px);
        width: 100%;
        max-width: 100%;
        height: 100%;
        transition: left 500ms cubic-bezier(0, 1, .5, 1);
        background-color: color('white');
        box-shadow: 0 1px 16px 0 rgba(0, 0, 0, .5);
        overflow: auto;
        z-index: z('menu', 'base');
    
        @media screen and (min-width: 425px) {
          left: calc(-66% - 16px);
          width: 66%;
        }
    
        &.js-opened {
          left: 0;
        }
    
        @include tablet {
          display: block !important; // sass-lint:disable-line no-important
          position: relative;
          left: 0;
          width: auto;
          height: auto;
          padding-top: 0;
          transition: none;
          background: transparent !important; // sass-lint:disable-line no-important
          box-shadow: none;
        }
    
        .header {
          position: relative;
          margin: .6rem $gutter-width 1.6rem;
    
          @include tablet {
            display: none;
          }
    
          &::before,
          &::after {
            @include theme('border-color', 'color-primary--lighten-4', 'header-border-bottom');
    
            position: absolute;
            top: 100%;
            width: 100%;
            height: 1rem;
            margin-top: .6rem;
            border-top: 2px solid;
            content: '';
          }
    
          &::before {
            left: -$gutter-width;
            width: 3rem + $gutter-width;
            border-right: 2px solid;
            border-top-right-radius: border-radius('radius-4');
          }
    
          &::after {
            right: -$gutter-width;
            // viewport - ::before + border-width + gutter-width
            width: calc(100% - 3rem + 2px + #{$gutter-width});
            border-left: 2px solid;
            border-top-left-radius: border-radius('radius-4');
          }
    
          button {
            position: absolute;
            right: 0;
            margin-top: .25rem;
          }
        }
      }
    
      .overlay.js-opened {
        position: fixed;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        background: rgba(255, 255, 2555, .8);
        z-index: z('menu', 'overlay');
    
        @include tablet {
          display: none;
        }
      }
    
      button {
        @include button-icon;
        padding: 0;
        border: 0;
        background: transparent;
        font-size: .8rem;
    
        @include tablet {
          display: none;
        }
    
        &::before {
          order: 0;
          margin-right: .5rem;
          margin-left: 0;
        }
    
        &.toggle {
          @include icon(hamburger);
        }
    
        &.close {
          @include icon(cross);
        }
      }
    
      ul {
        @extend %list-no-style;
        margin: 0;
    
        li {
          @include tablet {
            display: inline-block;
            margin-right: 1rem;
          }
    
          a,
          a[href^="mailto:"],
          a[download],
          a[href^="http://"],
          a[href^="https://"] {
            @include reset-link-background;
            @include link-underlined;
    
            margin-bottom: 1rem;
            padding: 1rem;
    
            @include tablet {
              margin-bottom: 0;
              padding: .5rem 0;
            }
          }
        }
      }
    }
    
  • URL: /components/raw/menu/_menu.scss
  • Filesystem Path: components/31-molecules/menu/_menu.scss
  • Size: 3 KB
  • Content:
    'use strict';
    
    (function () {
    
      if (!Menu) { // eslint-disable-line no-undef
        return;
      }
    
      const selected = document.querySelectorAll('nav.menu');
      for (let i = selected.length; i--;) {
        new Menu(selected[i]); // eslint-disable-line no-undef
      }
    
    })();
    
  • URL: /components/raw/menu/menu.bindings.js
  • Filesystem Path: components/31-molecules/menu/menu.bindings.js
  • Size: 263 Bytes
  • Content:
    'use strict';
    
    /* global define, module */
    (function (root, factory) {
      if (typeof define === 'function' && define.amd) {
        define(factory);
      }
      else {
        if (typeof exports === 'object') {
          module.exports = factory();
        }
        else {
          root.Menu = factory();
        }
      }
    }(this || window, function () {
    
      /**
       * Generates a tabTrap object
       *
       * @param {object} container DOM-element
       * @constructor
       */
      function TabTrap(container) {
        let focusables = getFocusables(container);
    
        /**
         * Returns all focusable elements within a given container.
         *
         * @param {object} container hamburger DOM-element
         * @return {array} focusable elements
         */
        function getFocusables(container) {
          let focusables = container.querySelectorAll(
            'a[href], ' +
            'area[href], ' +
            'input:not([disabled]):not([hidden]), ' +
            'select:not([disabled]), ' +
            'textarea:not([disabled]), ' +
            'button:not([disabled]), ' +
            '[tabindex="0"]'
          );
          return Array.prototype.slice.call(focusables);
        }
    
        this.setFocusables = function () {
          focusables = getFocusables(container);
        };
    
        this.next = function (e) {
          let active = document.activeElement;
          if (active && active === focusables[focusables.length - 1]) {
            focusables[0].focus();
            e.preventDefault();
          }
        };
    
        this.back = function (e) {
          let active = document.activeElement;
          if (active && active === focusables[0]) {
            focusables[focusables.length - 1].focus();
            e.preventDefault();
          }
        };
    
        this.home = function () {
          focusables[0].focus();
        };
    
        this.end = function () {
          focusables[focusables.length - 1].focus();
        };
    
        this.hasFocusables = focusables && focusables.length > 0;
      }
    
      /**
       * Toggle a scroll lock on a visible parent modal or on the body.
       *
       * @param {boolean} release or lock parent.
       * @param {object} elem DOM element.
       * @param {string} [parentSelector] Queryselectorstring.
       */
      const toggleScrollLockParent = (release, elem, parentSelector) => {
        parentSelector = parentSelector || '.modal.visible';
    
        const parentModal = elem.parentNode.closest(parentSelector);
        if (parentModal) {
          parentModal.style.overflow = release ? '' : 'hidden';
        }
        else {
          document.body.style.overflow = release ? '' : 'hidden';
        }
      };
    
      return function (elem, options) {
        const openbtn = elem.querySelector('.toggle');
        const closebtn = elem.querySelector('.close');
        const drawer = elem.querySelector('.drawer');
        const overlay = elem.querySelector('.overlay');
    
        const tabTrap = new TabTrap(drawer); // eslint-disable-line no-undef
    
        /**
         * Closes the hamburger menu
         *
         * @param {event} e onclick or keydown:escape
         */
        const close = (e) => {
          if (e) {
            e.preventDefault();
          }
    
          drawer.classList.remove('js-opened');
          overlay.classList.remove('js-opened');
          document.removeEventListener('keydown', handleKeyboardInput);
    
          // return focus to the trigger
          if (openbtn) {
            openbtn.focus();
            openbtn.setAttribute('aria-expanded', false);
          }
    
          toggleScrollLockParent(true, drawer);
    
          // remove the menu from the tabindex
          setTimeout(function () {
            drawer.style.display = 'none';
          }, 500);
        };
    
        /**
         * Opens the hamburger menu
         *
         * @param {event} e onclick
         */
        const open = (e) => {
          if (e) {
            e.preventDefault();
          }
    
          // add the menu to the tabindex
          drawer.style.display = 'block';
    
          setTimeout(function () {
            drawer.classList.add('js-opened');
            overlay.classList.add('js-opened');
          });
    
          // remember the trigger
          // trigger = e.target;
          openbtn.setAttribute('aria-expanded', true);
    
          // set focus to the menu
          drawer.focus();
    
          // handle keyboard input
          document.addEventListener('keydown', handleKeyboardInput);
    
          toggleScrollLockParent(false, drawer);
        };
    
        /**
         * Handle keyboard input
         *
         * @param {object} e event
         */
        const handleKeyboardInput = (e) => {
    
          if (!tabTrap || !tabTrap.hasFocusables || !e) {
            return;
          }
    
          var keyCode = e.keyCode || e.which;
    
          switch (keyCode) {
            case 9: // tab
              if (e.shiftKey) {
                tabTrap.back(e);
              }
              else {
                tabTrap.next(e);
              }
              break;
            case 40: // arrow down
            case 39: // arrow right
              tabTrap.next(e);
              break;
            case 38: // arrow up
            case 37: // arrow left
              tabTrap.back(e);
              break;
            case 36: // home
              e.preventDefault();
              tabTrap.home();
              break;
            case 35: // end
              e.preventDefault();
              tabTrap.end();
              break;
            case 27: // esc
              close(e);
              break;
          }
        };
    
        /**
         * Add event listeners.
         */
        openbtn.addEventListener('click', open);
        closebtn.addEventListener('click', close);
        overlay.addEventListener('click', close);
    
        return {
          open, close
        };
      };
    }));
    
  • URL: /components/raw/menu/menu.functions.js
  • Filesystem Path: components/31-molecules/menu/menu.functions.js
  • Size: 5.3 KB