Guidance

Please note that this component is part of the One Leeds Design System project (also known as DS2). It is currently intended for use in sandbox, development and test environments only and must not be implemented in production.

Working alongside the three tiered top navigation component, the secondary navigation component offers an extra level for splitting up page content.

This component is optional and there must be only one on any page. The links can point to different pages, or anchor target sections within the same page.

When implemented on a page with a hero banner this component should appear directly underneath the banner.

Sticky feature

Sticky refers to when an item on a page stops at a static vertical point on a page even when there is further page scrolling. This applies on desktop view, and “sticks” directly underneath the top navigation.

Two mechanisms for horizontal scrolling

When the number of options and screen size is such that all options can not fit within the horizontal constraints of the screen, scrolling is required. This is achieved firstly via left and right buttons, these appear at the beginning and end of the options where required. At the beginning of the list the left hand button is disabled, and at the end of the list the right hand button is disabled.

On touch screen devices the user can swipe left and right to move through the options.

Selected option highlighted

Highlight the option which corresponds to the selected page with a ‘selected’: true parameter (see example below). This highlights this option and shows the user which page they are on. This selected item will be placed in the first position. Please note developer guidance below for how to mark an option as current/highlighted.

Theme

For this release there are only a small number of themes (see below). These set the colour theme for this component. Further releases shall have more options.

Developer guidance - sample config

The below config example shows a config example.

Theme: optional parameter. If let blank then a default monochrome theme is applied. Other theme options are green, lilac and teal.

Link: This is an array of items each with a compulsory title and URL. This URL can be a separate page or section with the same page.

Selected: As per above, setting selected as true highlights this option. There must only be one item highlighted with this value.

'context': {
  'theme': 'lilac',
  'links': [
    {
      'title': 'Option 1',
      'url': '#',
    },
    {
      'title': 'Option 2',
      'url': '#',
    },
    {
      'title': 'Option 3',
      'url': '#',
      'selected': true,
    },
<nav class="ds2-secondary-nav {% if theme %}ds2-secondary-nav--{{ theme }}{% endif %}">
  <div class="ds2-secondary-nav__inner">
    <div class="ds2-secondary-nav__arrow-container ds2-secondary-nav__arrow-container--left">
      <button class="ds2-secondary-nav__arrow">&#x2190;</button>
    </div>
    <ul class="ds2-secondary-nav__links">
    {% for link in links %}
      <li><a class="ds2-secondary-nav__link {% if link.selected %}ds2-secondary-nav__link--selected{% endif %}" href="{{ link.url }}">{{ link.title }}</a></li>
    {% endfor %}
    </ul>
    <div class="ds2-secondary-nav__arrow-container  ds2-secondary-nav__arrow-container--right">
      <button class="ds2-secondary-nav__arrow">&#x2192;</button>
    </div>
  </div>
</nav>
<nav class="ds2-secondary-nav ">
    <div class="ds2-secondary-nav__inner">
        <div class="ds2-secondary-nav__arrow-container ds2-secondary-nav__arrow-container--left">
            <button class="ds2-secondary-nav__arrow">&#x2190;</button>
        </div>
        <ul class="ds2-secondary-nav__links">

            <li><a class="ds2-secondary-nav__link ds2-secondary-nav__link--selected" href="#">Suboption 1</a></li>

            <li><a class="ds2-secondary-nav__link " href="#">Suboption 2</a></li>

            <li><a class="ds2-secondary-nav__link " href="#">Suboption 3</a></li>

            <li><a class="ds2-secondary-nav__link " href="#">Suboption 4</a></li>

            <li><a class="ds2-secondary-nav__link " href="#">Suboption 5</a></li>

        </ul>
        <div class="ds2-secondary-nav__arrow-container  ds2-secondary-nav__arrow-container--right">
            <button class="ds2-secondary-nav__arrow">&#x2192;</button>
        </div>
    </div>
</nav>
  • Content:
    @mixin ds2-link--style($hoverColor: #111,) {
      &:focus {
      border: 2px solid black;
      outline: none;
    }
      &:hover {
        text-decoration: underline;
        text-decoration-thickness: 2px;
        text-underline-offset: 4px;
        color: $hoverColor;
        text-decoration-color: $hoverColor;
      }
    }
    
    @mixin ds2-focus--white {
      outline: 2px solid #fff;
      outline-offset: 1px;
    }
    
    .ds2-secondary-nav {
      $arrowSize: 32px;
      $navButtonSize: 60px;
      $secondaryNavGaps: 24px;
      color: black;
      margin: 0 auto;
      display: flex;
      justify-content: center;
      background: $color-grey--faded;
    
      // to stop scrollbar showing
      ::-webkit-scrollbar{
        width: 0;
        height: 0
      }
    
      ::-webkit-scrollbar-thumb{
        background:transparent
      }
    
      ::-webkit-scrollbar-track {
        background-color:transparent
      }
    
      &__inner {
        width: 100%;
        max-width: 1400px;
        display: flex;
        flex-direction: row;
        justify-content: space-between;
        align-items: center;
        position: relative;
    
        @include media(">=uol-media-m") {
          justify-content: center;
        }
      }
    
      &__links {
        display: flex;
        align-items: center;
        justify-content: left;
        overflow: auto;
        white-space: nowrap;
        scroll-behavior: smooth;
        max-width: calc(100% - 2 * #{$navButtonSize});
        margin: 0;
        padding: 0;
        left: $navButtonSize;
        list-style: none;
        visibility: hidden;
    
        @include media(">=uol-media-m") {
          position: relative;
          justify-content: center;
          left: 0;
          visibility: visible;
        }
      }
    
      &__link {
        font-family: uolInter, Arial, Helvetica, sans-serif;
        font-size: 16px;
        font-style: normal;
        font-weight: 400;
        text-decoration: none;
        padding: 16px 24px;
        border-radius: 6px;
    
        @include media(">=uol-media-m") {
          display: flex;
        }
        border: 2px solid transparent;
    
        &:active,
        &:hover {
          text-decoration: underline;
        }
    
        @include ds2-link--style;
      }
    
      &__arrow-container {
        width: $navButtonSize;
        height: 80px;
        display: flex;
        align-items: center;
        justify-content: center;
        z-index: 10;
    
        &--disabled {
          opacity: 0.2;
        }
    
    
      }
    
      &__arrow {
        background: inherit;
        border: 0;
        padding: 0;
        appearance: none;
        color: inherit;
        font-weight: bold;
        height: $arrowSize;
        width: $arrowSize;
        display: flex;
        justify-content: space-between;
        align-items: center;
        justify-content: center;
        font-size: 16px;
        z-index: 2;
    
        &:focus {
         @include ds2-link--style;
        }
      }
    
      &__link--selected {
        background: $color-brand-teal;
        display: flex;
      }
    }
    
    // theme options
    
    // default
    
    .ds2-secondary-nav {
      color: black;
      background: $color-brand-black-5;
    
      .ds2-secondary-nav__link--selected {
        color: white;
        background: $color-brand-black-90;
      }
    }
    
    // green
    .ds2-secondary-nav--green {
      .ds2-secondary-nav__link--selected {
        background: $color-brand-vivid-green;
      }
    }
    
    
    
    // lilac
    .ds2-secondary-nav--lilac {
      color: black;
      background: #ece8ff;
    
      .ds2-secondary-nav__link--selected {
        color: black;
        background: $color-brand-lilac;
      }
    }
    
    // teal
    .ds2-secondary-nav--teal {
      color: black;
      background: #dcf1ec;
    
      .ds2-secondary-nav__link--selected {
        color: black;
        background: #00d0a0;
      }
    }
    
    
    
    
    
  • URL: /components/raw/secondary-navigation/_secondary-nav.scss
  • Filesystem Path: src/library/ds2/02-components/secondary-nav/_secondary-nav.scss
  • Size: 3.3 KB
  • Content:
    export const ds2Secondary = () => {
      document.addEventListener("DOMContentLoaded", function () {
        const secondaryNav = document.querySelectorAll('.ds2-secondary-nav');
    
        secondaryNav.forEach( ( nav ) => {
    
          let currentItem = 0; // tracks which item is showing in furthest left position
          /*
          Following variable used to stop left/right arrow function running THEN scroll function
          Both functions incremement the current position and don't want it running twice
          */
          let keyJustPressed = false;
    
          const rightArrow = nav.querySelectorAll('.ds2-secondary-nav__arrow-container--right')[0];
          const leftArrow = nav.querySelectorAll('.ds2-secondary-nav__arrow-container--left')[0];
          const linksContainer = nav.querySelectorAll('.ds2-secondary-nav__links')[0];
          const links = linksContainer.querySelectorAll('.ds2-secondary-nav__link');
    
          // function to determine if arrows are disabled or not
          let currentItemCheck = () => {
            const leftButton = leftArrow.querySelector('button');
            const rightButton = rightArrow.querySelector('button');
    
            if (currentItem == 0) {
              leftButton.classList.add('ds2-secondary-nav__arrow-container--disabled');
              leftButton.setAttribute('disabled', '');
            } else {
              leftButton.classList.remove('ds2-secondary-nav__arrow-container--disabled');
              leftButton.removeAttribute('disabled');
            }
            if (currentItem == links.length - 1) {
              rightButton.classList.add('ds2-secondary-nav__arrow-container--disabled');
              rightButton.setAttribute('disabled', '');
            } else {
              rightButton.classList.remove('ds2-secondary-nav__arrow-container--disabled');
              rightButton.removeAttribute('disabled');
            }
          };
    
          /*
          links container is set to not show in CSS until position establised to stop visual jump to selected item
          here, set links to show whenever there's a window resize
          */
          window.addEventListener('resize', () => {
            linksContainer.style.visibility = "visible";
          });
    
          // add arrows to the left and right of the links container if needed
          const updateOverflow = () => {
            const linksOverflowContainer = linksContainer.scrollWidth > linksContainer.clientWidth;
            nav.querySelectorAll('.ds2-secondary-nav__arrow').forEach((arrow) => {
              arrow.style.display = linksOverflowContainer ? '' : 'none';
            });
            return linksOverflowContainer;
          };
          let linksOverflowContainer = updateOverflow();
          window.addEventListener('resize', () => {
            linksOverflowContainer = updateOverflow();
          });
          // Needs to be if links dont fit on screen, then show arrows and allow scroll to selected item
          if (linksOverflowContainer) {
            console.log("links overflow container");
            /*
            Current item on mobile is in left most position
            Extend margin of last item to allow this
            */
            let lastItemMargin = window.innerWidth - links[links.length - 1].offsetWidth - 120;
            links[links.length - 1].style.marginRight = lastItemMargin + "px";
    
            // get left positions of each item (so arrows can move in to position)
            const linksX = [];
            linksX[0] = 0;
            let currentX = 0;
            for (let i = 1; i < links.length; i++) {
              linksX[i] = currentX + links[i-1].offsetWidth;
              currentX +=  links[i-1].offsetWidth;
    
              /*
              by default scroll is at first item
              if another item has selected class, move scroll to this item
              */
              if (links[i-1].classList.contains("ds2-secondary-nav__link--selected")) {
                currentItem = i - 1;
                linksContainer.scrollTo({
                  left: linksX[currentItem],
                  behavior: 'smooth'
                });
              }
            }
            /*
            above will have put scroll in to correct position
            now can show the links without a visible jump
            */
            linksContainer.style.visibility = "visible";
            currentItemCheck();
            /*
            When using scroll bar we need to adjust current item (used by arrows)
            to correlate scroll with arrows
            */
            linksContainer.addEventListener('scroll', (event) => {
              if (keyJustPressed == false) {
                const currentLeft = event.target.scrollLeft;
    
                // variable to see if item changed (if yes, then run currentItemCheck function)
                let tempCurrentItem = currentItem;
                for (let i = 0; i < links.length; i++) {
                  if (currentLeft > linksX[i]) {
                    currentItem = i + 1;
                  }
                  if (currentLeft == 0) {
                    currentItem = 0;
                  }
                }
    
                if (tempCurrentItem != currentItem || currentItem == 0) {
                  currentItemCheck();
                }
                keyJustPressed = false;
              }
              keyJustPressed = false;
            });
    
            // Right arrow pressed, changes currentItem and scrolls to next item (if not last item)
            rightArrow.addEventListener("click", () => {
              if (currentItem < links.length - 1) {
                currentItem += 1;
                keyJustPressed = true;
                currentItemCheck();
                linksContainer.scrollTo({
                  left: linksX[currentItem],
                  behavior: 'smooth'
                });
              }
            });
    
            // Left arrow pressed, changes currentItem and scrolls to previous item (if not first item)
            leftArrow.addEventListener("click", () => {
              if (currentItem > 0) {
                currentItem -= 1;
                keyJustPressed = true;
                currentItemCheck();
                linksContainer.scrollTo({
                  left: linksX[currentItem],
                  behavior: 'smooth'
                });
              }
    
            });
          }
        });
      });
    }
    
  • URL: /components/raw/secondary-navigation/secondary-nav.module.js
  • Filesystem Path: src/library/ds2/02-components/secondary-nav/secondary-nav.module.js
  • Size: 5.9 KB
{
  "links": [
    {
      "title": "Suboption 1",
      "url": "#",
      "selected": true
    },
    {
      "title": "Suboption 2",
      "url": "#"
    },
    {
      "title": "Suboption 3",
      "url": "#"
    },
    {
      "title": "Suboption 4",
      "url": "#"
    },
    {
      "title": "Suboption 5",
      "url": "#"
    }
  ]
}