side navigation

A side navigation can be used to enable access to first and second level pages. Second level pages can forma group of sub items.

  • The navigation can be opened and closed by clicking the trigger menu icon on the top left.
  • A click on a First Level Page menu item opens the corresponding page. Selecting a group opens or closes it, and closes a previous opened group.
  • In the desktop viewport, when the side navigation is closed is seen as a narrow sidebar. When opened appears as a full-surface overlay panel.

See the example below, the API documentation as well as JS example code at the end of this page.


This component works with all its features only in browsers that have support for the :has CSS selector. Please refer to the reference on MDN.
If you need to run this component in a browser with no support for the :has CSS selector, please use the FROK Release 3.6.x.

component variations

Default

This component is only meant to be used in a full page and won't render / behave correctly here. Please use the button below (Open on blank page) to see the component in action.
<style>
  @media (min-width: 1194px) {
    .a-button[data-frok-action="show"] {
      display: none;
    }
  }
</style>
<div class="frontend-kit-example_side-navigation">
  <nav
    class="m-side-navigation -contrast"
    aria-label="Side Navigation"
    aria-hidden="false"
  >
    <div class="m-side-navigation__header">
      <div class="m-side-navigation__header__label -size-l highlight">
        App name
      </div>
      <button
        type="button"
        class="a-button a-button--integrated -without-label m-side-navigation__header__trigger -open"
        aria-haspopup="false"
        aria-label="Open Side Navigation"
        tabindex="0"
      >
        <i
          class="a-icon a-button__icon boschicon-bosch-ic-list-view-mobile"
        ></i>
      </button>
      <button
        type="button"
        class="a-button a-button--integrated -without-label m-side-navigation__header__trigger -close"
        aria-haspopup="false"
        aria-label="Close Side Navigation"
        tabindex="-1"
      >
        <i class="a-icon a-button__icon boschicon-bosch-ic-close"></i>
      </button>
    </div>
    <ul class="m-menu-group" role="menubar" aria-orientation="vertical">
      <li class="a-menu-item" role="none">
        <div class="a-menu-item__wrapper">
          <a
            href="#"
            role="menuitem"
            class="a-menu-item__link"
            aria-disabled="false"
          >
            <i class="a-icon boschicon-bosch-ic-login"></i>
            <span class="a-menu-item__label">Login</span>
          </a>
        </div>
      </li>
      <li class="a-menu-item" role="none">
        <div class="a-menu-item__wrapper">
          <a
            href="#"
            role="menuitem"
            class="a-menu-item__link"
            aria-disabled="false"
          >
            <i class="a-icon boschicon-bosch-ic-chat"></i>
            <span class="a-menu-item__label">Contact</span>
          </a>
        </div>
      </li>
      <li class="a-menu-item" role="none">
        <div class="a-menu-item__wrapper">
          <button
            type="button"
            role="menuitem"
            class="a-menu-item__group"
            aria-disabled="false"
            aria-controls="group-id-1"
            aria-label="open group"
          >
            <i class="a-icon boschicon-bosch-ic-battery-0"></i>
            <span class="a-menu-item__label">Group</span>
            <i class="a-icon ui-ic-down-small"></i>
          </button>
        </div>
        <ul id="group-id-1" class="m-menu-group__group" role="menu">
          <li class="a-menu-item -indent" role="none">
            <div class="a-menu-item__wrapper">
              <a
                href="#"
                role="menuitem"
                class="a-menu-item__link"
                aria-disabled="false"
              >
                <span class="a-menu-item__label">label 1</span>
              </a>
            </div>
          </li>
          <li class="a-menu-item -disabled -indent" role="none">
            <div class="a-menu-item__wrapper">
              <a
                href="#"
                role="menuitem"
                class="a-menu-item__link"
                aria-disabled="true"
                tabindex="-1"
              >
                <span class="a-menu-item__label">label 2</span>
              </a>
            </div>
          </li>
          <li class="a-menu-item -indent" role="none">
            <div class="a-menu-item__wrapper">
              <a
                href="#"
                role="menuitem"
                class="a-menu-item__link"
                aria-disabled="false"
              >
                <span class="a-menu-item__label">label 3</span>
              </a>
            </div>
          </li>
        </ul>
      </li>
      <li class="a-menu-item -disabled" role="none">
        <div class="a-menu-item__wrapper">
          <button
            type="button"
            role="menuitem"
            class="a-menu-item__group"
            aria-disabled="true"
            tabindex="-1"
            aria-controls="group-id-2"
            aria-label="open group"
          >
            <i class="a-icon boschicon-bosch-ic-bicycle-e"></i>
            <span class="a-menu-item__label">
              Group 2 with some extended labels
            </span>
            <i class="a-icon ui-ic-down-small"></i>
          </button>
        </div>
        <ul id="group-id-2" class="m-menu-group__group" role="menu">
          <li class="a-menu-item -indent" role="none">
            <div class="a-menu-item__wrapper">
              <a
                href="#"
                role="menuitem"
                class="a-menu-item__link"
                aria-disabled="false"
              >
                <span class="a-menu-item__label">label 1</span>
              </a>
            </div>
          </li>
          <li class="a-menu-item -indent" role="none">
            <div class="a-menu-item__wrapper">
              <a
                href="#"
                role="menuitem"
                class="a-menu-item__link"
                aria-disabled="false"
              >
                <span class="a-menu-item__label">label 2</span>
              </a>
            </div>
          </li>
        </ul>
      </li>
      <li class="a-menu-item" role="none">
        <div class="a-menu-item__wrapper">
          <button
            type="button"
            role="menuitem"
            class="a-menu-item__group"
            aria-disabled="false"
            aria-controls="group-id-3"
            aria-label="open group"
          >
            <i class="a-icon boschicon-bosch-ic-agility"></i>
            <span class="a-menu-item__label">
              Group 3 with some extended labels
            </span>
            <i class="a-icon ui-ic-down-small"></i>
          </button>
        </div>
        <ul id="group-id-3" class="m-menu-group__group" role="menu">
          <li class="a-menu-item -indent" role="none">
            <div class="a-menu-item__wrapper">
              <a
                href="#"
                role="menuitem"
                class="a-menu-item__link"
                aria-disabled="false"
              >
                <span class="a-menu-item__label">label 1</span>
              </a>
            </div>
          </li>
          <li class="a-menu-item -indent" role="none">
            <div class="a-menu-item__wrapper">
              <a
                href="#"
                role="menuitem"
                class="a-menu-item__link"
                aria-disabled="false"
              >
                <span class="a-menu-item__label">label 2</span>
              </a>
            </div>
          </li>
        </ul>
      </li>
      <li class="a-menu-item" role="none">
        <div class="a-menu-item__wrapper">
          <a
            href="#"
            role="menuitem"
            class="a-menu-item__link"
            aria-disabled="false"
          >
            <i class="a-icon boschicon-bosch-ic-atom"></i>
            <span class="a-menu-item__label">atom</span>
          </a>
        </div>
      </li>
      <li class="a-menu-item -disabled" role="none">
        <div class="a-menu-item__wrapper">
          <a
            href="#"
            role="menuitem"
            class="a-menu-item__link"
            aria-disabled="true"
            tabindex="-1"
          >
            <i class="a-icon boschicon-bosch-ic-fax"></i>
            <span class="a-menu-item__label">fax</span>
          </a>
        </div>
      </li>
    </ul>
  </nav>
  <div
    style="align-items:center;display:flex;height:100vh;justify-content:center;width:100vw"
  >
    <button
      type="button"
      class="a-button a-button--primary -without-icon"
      data-frok-action="show"
    >
      <span class="a-button__label">click me</span>
    </button>
  </div>
</div>

additional content

demo

export default (): void => {
  const sideNavigationExamples = document.getElementsByClassName(
    'frontend-kit-example_side-navigation',
  );

  [...sideNavigationExamples].forEach((container) => {
    const trigger = container.querySelector(
      '.a-button[data-frok-action="show"]',
    );

    const componentContainer =
      container.getElementsByClassName('m-side-navigation')[0];

    const linkItems = container.querySelectorAll(
      '.a-menu-item:has(> .a-menu-item__wrapper > .a-menu-item__link)',
    );

    const updateActiveState = (item: HTMLElement, items: HTMLElement[]) => {
      // reset active states
      items.forEach((element: HTMLElement) => {
        element.classList.remove('-selected');
      });

      // put active state on clicked link
      item.classList.add('-selected');
    };

    linkItems.forEach((linkItem) =>
      linkItem.addEventListener('click', () =>
        updateActiveState(linkItem, linkItems),
      ),
    );

    const sideNavigation = componentContainer.component;
    trigger.addEventListener('click', () => sideNavigation.show());
    sideNavigation.setExternalTrigger(trigger);
  });
};

styles SCSS

/* stylelint-disable max-nesting-depth */
/* stylelint-disable no-descending-specificity */
/* stylelint-disable a11y/no-display-none */

$m-side-navigation--open-width: 19rem;

.m-side-navigation {
  position: fixed;
  height: 100%;
  min-height: 100vh;
  overflow: scroll;
  left: 0;
  top: 0;
  width: 0;
  transition: 125ms width $default-transition-easing;

  // remove visible scroll bars
  -ms-overflow-style: none;
  scrollbar-width: none; 
  &::-webkit-scrollbar {
    display: none;
  }

  // Reset
  ul {
    margin-bottom: 0;
  }

  a,
  a:visited {
    text-decoration: none;
  }

  /* App's name and Open / Close button */
  &__header {
    align-items: center;
    display: flex;
    justify-content: space-between;
    margin-bottom: 1rem;
    position: relative;

    &__label {
      display: none;
      padding: 0.5rem 1.25rem 0.5rem 1rem;
      white-space: nowrap;
      overflow: hidden;
      text-overflow: ellipsis;
    }

    &__trigger {
      display: flex;
      position: relative;

      &:hover {
        background-color: var(--plain__enabled__fill__hovered);
        color: var(--plain__enabled__front__hovered);
      }

      &:active {
        background-color: var(--plain__enabled__fill__pressed);
        color: var(--plain__enabled__front__pressed);
      }

      &:focus-visible {
        @include focus-inside;
      }

      &.-close {
        display: none;
      }
    }
  }

  &:not(.-open) .a-menu-item {
    &__link,
    &__button,
    &__group {
      column-gap: 0;
      padding-inline-end: 0.75rem;
    }

    &__group .a-icon.ui-ic-down-small {
      display: none;
    }
  }

  /* First Level Section */
  &__menuItems {
    padding: 0;

    .m-side-navigation__menuSubitems {
      display: none;
      padding: 0;
    }
  }

  // When the menu is open.
  // Classes Logic:
  // -opening = Menu is opening / the transition is happening.
  // -open = Menu is open / the transition has ended.
  &.-opening,
  &.-open {
    width: 100vw;

    .m-side-navigation {
      &__header {
        &__label {
          display: flex;
          column-gap: 1rem;
        }

        &__trigger {
          &.-open {
            display: none;
          }

          &.-close {
            display: flex;
          }
        }
      }

      &__menuSubitems {
        width: 100%;
      }
    }
  }

  .a-menu-item__wrapper.-open {
    + .m-side-navigation__menuSubitems {
      display: flex;
      flex-direction: column;
      padding: 0;
      width: 100%;
    }
  }
}

@include tablet-and-up {
  .m-side-navigation.-open,
  .m-side-navigation.-opening {
    width: $m-side-navigation--open-width;
  }
}

@include desktop-and-up {
  .m-side-navigation {
    width: 3rem;
  }
}
/* stylelint-enable a11y/no-display-none */
/* stylelint-enable no-descending-specificity */
/* stylelint-enable max-nesting-depth */