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.

The top navigation presents the University of Leeds logo, site search and the main navigation for the site.

The top nav must be present on every page of the site, and must remain consistent throughout the site, ie. every page on the same domain or subdomain should have the same navigation.

The University of Leeds logo must always link to the main University of Leeds homepage.

On smaller screens the navigation is shown in a menu opened by a “Menu” button, with each level sliding into view in turn. On larger screens the top level links are shown in a row, each opening a full width dropdown.

Only one top nav should be used on a page.

Developer guidance

Ensure that the logo label and url are always set to:

"logo": {
  "url": "https://www.leeds.ac.uk/",
  "label": "University of Leeds homepage"
}

The logo images are hidden from Assistive Technology, so the label is what names the link.

The search form must have a label. It is hidden visually, so it should start with the word “Search”, the same as the button users see.

Example:

"search": {
  "action": "https://www.leeds.ac.uk/search",
  "inputName": "q",
  "label": "Search leeds.ac.uk",
  "placeholder": "Search",
  "submitLabel": "Search",
  "openLabel": "Search",
  "closeLabel": "Close search"
}

The submitLabel, openLabel and closeLabel name the icon buttons for Assistive Technology users. They must always be set.

Extra values can be sent with the search using hiddenFields, as a list of name and value pairs.

The navigation supports up to three levels, using level1Options, level2Options and level3Options.

Example

  • Study
    • Course search
    • Undergraduate
      • Undergraduate home page
      • Clearing
      • Fees and funding
    • …
  • Research
  • …

The component does not support going beyond three levels.

Items with children

An item with children becomes a button that opens its children. It does not link anywhere, so any url given to it is ignored.

If the parent item also has a page of its own, add that page as the first child.

Example:

{
  "title": "Undergraduate",
  "level3Options": [
    {
      "title": "Undergraduate home page",
      "url": "https://www.leeds.ac.uk/undergraduate"
    },
    ...
  ]
}
<header class="ds2-top-nav">
  <div class="ds2-top-nav__bar">

    {# Inline SVG rather than <img> so the logos take currentColor and stay visible in high-contrast mode. #}
    <a class="ds2-top-nav__logo" href="{{ logo.url }}">
      <svg class="ds2-top-nav__logo-crest" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 261 261.2" aria-hidden="true" focusable="false">
        <polygon points="142,168.4 146.6,168.4 146.6,126.8 156.8,126.8 156.8,119.2 142,119.2"></polygon>
        <polygon points="162.4,168.4 167,168.4 167,126.8 177.2,126.8 177.2,119.2 162.4,119.2"></polygon>
        <polygon points="169,83.4 173.2,83.4 173.2,79.6 166.4,79.6 166.4,91 169,91"></polygon>
        <polygon points="157,83.4 161.2,83.4 161.2,79.6 154.4,79.6 154.4,91 157,91"></polygon>
        <polygon points="182.8,168.4 187.4,168.4 187.4,126.8 197.6,126.8 197.6,119.2 182.8,119.2"></polygon>
        <path d="M184.2,206.3c0-8.4-6.6-14.2-14.4-14.2s-14.4,5.8-14.4,14.2c0,8.4,6.6,14.2,14.4,14.2
          S184.2,214.7,184.2,206.3z M160.8,206.3c0-5.4,4.2-9,9-9s9,3.6,9,9c0,5.4-4.2,9-9,9S160.8,211.7,160.8,206.3z"></path>
        <polygon points="137.2,112.2 202.4,112.2 205.2,108 134.4,108"></polygon>
        <path d="M0,0.1v261h114.2V192H142v69.1h4.6v-77.7H193v77.7h4.6V192h27.8v69.1H261V0.1H0z M169.6,30.1h0.4l26.8,16.4
          h-54L169.6,30.1z M142.4,49.7h54.8l2.8,19.8h-60.4L142.4,49.7z M219.6,187.4h-22v-8.6H142v8.6h-22v-19.2h3.8v-62.8h6V72.8h80v32.6
          h6v62.8h3.8V187.4z"></path>
        <polygon points="181,83.4 185.2,83.4 185.2,79.6 178.4,79.6 178.4,91 181,91"></polygon>
      </svg>
      <svg class="ds2-top-nav__logo-full" xmlns="http://www.w3.org/2000/svg" viewBox="24 24 213.22 43" aria-hidden="true" focusable="false">
        <path d="M50.1623 37.7708H50.8622V37.1486H49.729V39.0154H50.1623V37.7708Z"></path>
        <path d="M51.0625 51.6899H51.829V44.8777H53.5288V43.6332H51.0625V51.6899Z"></path>
        <path d="M47.6631 51.6899H48.4296V44.8777H50.1294V43.6332H47.6631V51.6899Z"></path>
        <path d="M54.6952 57.8962C54.6952 56.5207 53.5954 55.5709 52.2956 55.5709C50.9958 55.5709 49.896 56.5207 49.896 57.8962C49.896 59.2718 50.9958 60.2216 52.2956 60.2216C53.5954 60.2216 54.6952 59.2718 54.6952 57.8962ZM50.7959 57.8962C50.7959 57.012 51.4957 56.4224 52.2956 56.4224C53.0955 56.4224 53.7954 57.012 53.7954 57.8962C53.7954 58.7805 53.0955 59.37 52.2956 59.37C51.4957 59.37 50.7959 58.7805 50.7959 57.8962Z"></path>
        <path d="M52.1623 37.7708H52.8622V37.1486H51.729V39.0154H52.1623V37.7708Z"></path>
        <path d="M54.1618 37.7708H54.8617V37.1486H53.7285V39.0154H54.1618V37.7708Z"></path>
        <path d="M54.4619 51.6899H55.2285V44.8777H56.9282V43.6332H54.4619V51.6899Z"></path>
        <path d="M24 24.13V66.87H43.0303V55.5546H47.6629V66.87H48.4294V54.1463H56.1615V66.87H56.9281V55.5546H61.5607V66.87H67.493V24.13H24ZM52.2621 29.0426H52.3288L56.7948 31.7282H47.7962L52.2621 29.0426ZM47.7295 32.2522H56.8614L57.328 35.4782H47.2629L47.7295 32.2522ZM60.5941 54.8013H56.9281V53.393H47.6629V54.8013H43.9968V51.6572H44.63V41.3734H45.6299V36.035H58.9611V41.3734H59.9609V51.6572H60.5941V54.8013Z"></path>
        <path d="M46.8631 42.487H57.728L58.1946 41.7992H46.3965L46.8631 42.487Z"></path>
        <path d="M220.477 24.2292L226.944 35.4296V42.7654H230.786V35.4623L237.22 24.2292H233.182L230.359 29.4364C229.899 30.2224 229.374 31.2049 228.98 32.0564H228.947C228.52 31.2049 228.028 30.1896 227.568 29.4037L224.745 24.2292H220.477ZM203.964 27.4059H209.512V42.7654H213.386V27.4059H218.934V24.2292H203.964V27.4059ZM197.168 42.7654H201.009V24.2292H197.168V42.7654ZM183.576 65.2643C184.922 66.1485 186.826 67 189.486 67C193.425 67 196.314 64.8385 196.314 61.3998C196.314 55.5704 188.304 55.9634 188.304 52.7867C188.304 51.6733 189.387 51.1165 190.766 51.1165C192.309 51.1165 193.622 51.6078 195.198 52.4593L195.264 49.0861C194.213 48.4966 192.539 47.9726 190.602 47.9726C186.498 47.9726 184.266 50.4615 184.266 53.278C184.266 54.719 184.725 55.7669 185.448 56.5857C187.713 59.1401 192.243 59.5659 192.243 62.0221C192.243 63.3976 190.733 63.8888 189.321 63.8888C187.647 63.8888 186.268 63.2011 185.054 62.4478L183.576 65.2643ZM181.114 41.2917C182.427 42.1759 184.364 43.0274 187.023 43.0274C190.963 43.0274 193.852 40.9642 193.852 37.3945C193.852 31.6961 185.809 31.9254 185.809 28.7814C185.809 27.7007 186.925 27.1112 188.271 27.1112C189.814 27.1112 191.16 27.6352 192.736 28.4867L192.801 25.1135C191.718 24.4912 190.044 24 188.107 24C184.003 24 181.803 26.489 181.803 29.3054C181.803 30.7464 182.23 31.7944 182.952 32.6131C185.251 35.1676 189.748 35.5605 189.748 38.0168C189.748 39.425 188.238 39.8835 186.859 39.8835C185.185 39.8835 183.773 39.2285 182.591 38.4752L181.114 41.2917ZM169.459 63.5286V51.4113H171.823C175.631 51.4113 177.962 53.7692 177.962 57.4699C177.962 61.1051 175.631 63.5286 171.823 63.5286H169.459ZM169.427 32.7768V27.4059H171.068C172.907 27.4059 173.957 28.4867 173.957 29.9604C173.957 31.5651 172.71 32.7768 171.068 32.7768H169.427ZM165.585 42.7654H169.427V35.5933H170.904C171.561 35.8553 172.677 37.9513 175.5 42.7654H179.932C177.109 38.0168 175.336 34.8728 174.384 34.3816V34.3488C176.091 33.5628 177.765 31.9581 177.765 29.5347C177.765 26.6855 175.96 24.2292 171.002 24.2292H165.585V42.7654ZM165.585 66.738H171.757C177.798 66.738 182 62.9391 182 57.3717C182 51.9353 178.126 48.2018 171.823 48.2018H165.585V66.738ZM151.436 42.7654H162.237V39.5887H155.31V34.8728H161.252V31.6961H155.31V27.4059H162.171V24.2292H151.436V42.7654ZM151.436 66.738H162.237V63.5613H155.277V58.8781H161.252V55.6687H155.277V51.3785H162.171V48.2018H151.436V66.738ZM137.319 66.738H148.12V63.5613H141.193V58.8781H147.135V55.6687H141.193V51.3785H148.054V48.2018H137.319V66.738ZM130.458 24.2292L138.173 42.8964H141.226L149.171 24.2292H145.231L141.16 34.1196C140.7 35.2986 140.274 36.4448 139.913 37.5583H139.847C139.453 36.4448 139.059 35.3313 138.567 34.1196L134.693 24.2292H130.458ZM124.318 42.7654H128.192V24.2292H124.318V42.7654ZM124.318 66.738H134.66V63.5613H128.192V48.2018H124.318V66.738ZM105.441 66.738H109.282V58.9436H115.028V55.7669H109.282V51.3785H116.045V48.2018H105.441V66.738ZM102.913 42.7654H106.754V32.6131C106.754 31.7616 106.754 30.6154 106.689 30.0259H106.754C107.247 30.8446 107.805 31.5651 108.527 32.4821L116.571 42.7654H120.182V24.2292H116.341V33.6611C116.341 34.4471 116.341 35.6588 116.374 36.543H116.341C115.815 35.757 115.323 35.0693 114.568 34.1196L106.787 24.2292H102.913V42.7654ZM86.5312 57.4699C86.5312 53.6382 89.0591 51.1493 92.4078 51.1493C95.7564 51.1493 98.2515 53.6382 98.2515 57.4699C98.2515 61.3343 95.7564 63.8233 92.4078 63.8233C89.0591 63.8233 86.5312 61.3343 86.5312 57.4699ZM83.0841 35.5278C83.0841 40.4402 85.809 43.0274 90.9961 43.0274C95.9862 43.0274 99.1051 40.5057 99.1051 35.4623V24.2292H95.2311V35.4296C95.2311 38.2788 93.8195 39.7852 91.0618 39.7852C88.4682 39.7852 86.958 38.2133 86.958 35.4296V24.2292H83.0841V35.5278ZM92.4078 67C98.0545 67 102.29 62.9718 102.29 57.4699C102.29 52.0008 98.0545 47.9398 92.4078 47.9398C86.7282 47.9398 82.4932 52.0008 82.4932 57.4699C82.4932 62.9718 86.7282 67 92.4078 67Z"></path>
      </svg>
      <span class="ds2-top-nav__visually-hidden">{{ logo.label }}</span>
    </a>

    <div class="ds2-top-nav__search-group">
    <button class="ds2-top-nav__search-close" type="button">
      <span class="ds2-top-nav__back-arrow" aria-hidden="true"></span>
      <span class="ds2-top-nav__visually-hidden">{{ search.closeLabel }}</span>
    </button>

    <form class="ds2-top-nav__search" id="ds2-top-nav-search" role="search" action="{{ search.action }}" method="get">
      <label class="ds2-top-nav__search-label ds2-top-nav__visually-hidden" for="ds2-top-nav-search-input">{{ search.label }}</label>
      <input class="ds2-top-nav__search-input" id="ds2-top-nav-search-input" name="{{ search.inputName }}" type="search" placeholder="{{ search.placeholder }}">
      {% for field in search.hiddenFields %}
        <input type="hidden" name="{{ field.name }}" value="{{ field.value }}">
      {% endfor %}
      <button class="ds2-top-nav__search-submit" type="submit">
        <svg class="ds2-top-nav__search-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" aria-hidden="true" focusable="false">
          <path d="M20.67,18.67H19.61l-.37-.36a8.66,8.66,0,1,0-.93.93l.36.37v1.06l6.66,6.65,2-2Zm-8,0a6,6,0,1,1,6-6A6,6,0,0,1,12.67,18.67Z"></path>
        </svg>
        <span class="ds2-top-nav__visually-hidden">{{ search.submitLabel }}</span>
      </button>
    </form>
    </div>

    <button class="ds2-top-nav__search-toggle" type="button" aria-controls="ds2-top-nav-search">
      <svg class="ds2-top-nav__search-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" aria-hidden="true" focusable="false">
        <path d="M20.67,18.67H19.61l-.37-.36a8.66,8.66,0,1,0-.93.93l.36.37v1.06l6.66,6.65,2-2Zm-8,0a6,6,0,1,1,6-6A6,6,0,0,1,12.67,18.67Z"></path>
      </svg>
      <span class="ds2-top-nav__visually-hidden">{{ search.openLabel }}</span>
    </button>

    <button class="ds2-top-nav__menu-toggle" type="button" aria-controls="ds2-top-nav-menu">
      <svg class="ds2-top-nav__menu-icon ds2-top-nav__menu-icon--burger" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 12" aria-hidden="true" focusable="false">
        <rect x="0" y="0" width="14" height="2"></rect>
        <rect x="9" y="5" width="15" height="2"></rect>
        <rect x="1" y="10" width="16" height="2"></rect>
      </svg>
      <svg class="ds2-top-nav__menu-icon ds2-top-nav__menu-icon--close" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true" focusable="false">
        <path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"></path>
      </svg>
      <span class="ds2-top-nav__visually-hidden">{{ menuLabel }}</span>
    </button>
  </div>

  <nav class="ds2-top-nav__nav" id="ds2-top-nav-menu" aria-label="{{ navLabel }}">
    <ul class="ds2-top-nav__list ds2-top-nav__list--level-1">
      {% for level1Option in level1Options %}
        {% if level1Option.level2Options %}
          <li class="ds2-top-nav__item ds2-top-nav__item--level-1 ds2-top-nav__item--has-children">
            <button class="ds2-top-nav__link ds2-top-nav__link--button" type="button">
              <span class="ds2-top-nav__link-text">{{ level1Option.title }}</span>
              <span class="ds2-top-nav__arrow" aria-hidden="true"></span>
            </button>

            <div class="ds2-top-nav__panel ds2-top-nav__panel--level-2">
              <button class="ds2-top-nav__back" type="button">
                <span class="ds2-top-nav__back-arrow" aria-hidden="true"></span>
                <span class="ds2-top-nav__visually-hidden">Back to main menu</span>
              </button>
              <ul class="ds2-top-nav__list ds2-top-nav__list--level-2">
                {% for level2Option in level1Option.level2Options %}
                  {% if level2Option.level3Options %}
                    <li class="ds2-top-nav__item ds2-top-nav__item--level-2 ds2-top-nav__item--has-children">
                      <button class="ds2-top-nav__link ds2-top-nav__link--button" type="button">
                        <span class="ds2-top-nav__link-text">{{ level2Option.title }}</span>
                        <span class="ds2-top-nav__arrow" aria-hidden="true"></span>
                      </button>

                      <div class="ds2-top-nav__panel ds2-top-nav__panel--level-3">
                        <button class="ds2-top-nav__back" type="button">
                          <span class="ds2-top-nav__back-arrow" aria-hidden="true"></span>
                          <span class="ds2-top-nav__visually-hidden">Back to {{ level1Option.title }}</span>
                        </button>
                        <ul class="ds2-top-nav__list ds2-top-nav__list--level-3">
                          {% for level3Option in level2Option.level3Options %}
                            <li class="ds2-top-nav__item ds2-top-nav__item--level-3">
                              <a class="ds2-top-nav__link" href="{{ level3Option.url }}">{{ level3Option.title }}</a>
                            </li>
                          {% endfor %}
                        </ul>
                      </div>
                    </li>
                  {% else %}
                    <li class="ds2-top-nav__item ds2-top-nav__item--level-2">
                      <a class="ds2-top-nav__link" href="{{ level2Option.url }}">{{ level2Option.title }}</a>
                    </li>
                  {% endif %}
                {% endfor %}
              </ul>
            </div>
          </li>
        {% else %}
          <li class="ds2-top-nav__item ds2-top-nav__item--level-1">
            <a class="ds2-top-nav__link" href="{{ level1Option.url }}">{{ level1Option.title }}</a>
          </li>
        {% endif %}
      {% endfor %}
    </ul>
  </nav>
</header>
<header class="ds2-top-nav">
    <div class="ds2-top-nav__bar">

        <a class="ds2-top-nav__logo" href="https://www.leeds.ac.uk/">
            <svg class="ds2-top-nav__logo-crest" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 261 261.2" aria-hidden="true" focusable="false">
                <polygon points="142,168.4 146.6,168.4 146.6,126.8 156.8,126.8 156.8,119.2 142,119.2"></polygon>
                <polygon points="162.4,168.4 167,168.4 167,126.8 177.2,126.8 177.2,119.2 162.4,119.2"></polygon>
                <polygon points="169,83.4 173.2,83.4 173.2,79.6 166.4,79.6 166.4,91 169,91"></polygon>
                <polygon points="157,83.4 161.2,83.4 161.2,79.6 154.4,79.6 154.4,91 157,91"></polygon>
                <polygon points="182.8,168.4 187.4,168.4 187.4,126.8 197.6,126.8 197.6,119.2 182.8,119.2"></polygon>
                <path d="M184.2,206.3c0-8.4-6.6-14.2-14.4-14.2s-14.4,5.8-14.4,14.2c0,8.4,6.6,14.2,14.4,14.2
          S184.2,214.7,184.2,206.3z M160.8,206.3c0-5.4,4.2-9,9-9s9,3.6,9,9c0,5.4-4.2,9-9,9S160.8,211.7,160.8,206.3z"></path>
                <polygon points="137.2,112.2 202.4,112.2 205.2,108 134.4,108"></polygon>
                <path d="M0,0.1v261h114.2V192H142v69.1h4.6v-77.7H193v77.7h4.6V192h27.8v69.1H261V0.1H0z M169.6,30.1h0.4l26.8,16.4
          h-54L169.6,30.1z M142.4,49.7h54.8l2.8,19.8h-60.4L142.4,49.7z M219.6,187.4h-22v-8.6H142v8.6h-22v-19.2h3.8v-62.8h6V72.8h80v32.6
          h6v62.8h3.8V187.4z"></path>
                <polygon points="181,83.4 185.2,83.4 185.2,79.6 178.4,79.6 178.4,91 181,91"></polygon>
            </svg>
            <svg class="ds2-top-nav__logo-full" xmlns="http://www.w3.org/2000/svg" viewBox="24 24 213.22 43" aria-hidden="true" focusable="false">
                <path d="M50.1623 37.7708H50.8622V37.1486H49.729V39.0154H50.1623V37.7708Z"></path>
                <path d="M51.0625 51.6899H51.829V44.8777H53.5288V43.6332H51.0625V51.6899Z"></path>
                <path d="M47.6631 51.6899H48.4296V44.8777H50.1294V43.6332H47.6631V51.6899Z"></path>
                <path d="M54.6952 57.8962C54.6952 56.5207 53.5954 55.5709 52.2956 55.5709C50.9958 55.5709 49.896 56.5207 49.896 57.8962C49.896 59.2718 50.9958 60.2216 52.2956 60.2216C53.5954 60.2216 54.6952 59.2718 54.6952 57.8962ZM50.7959 57.8962C50.7959 57.012 51.4957 56.4224 52.2956 56.4224C53.0955 56.4224 53.7954 57.012 53.7954 57.8962C53.7954 58.7805 53.0955 59.37 52.2956 59.37C51.4957 59.37 50.7959 58.7805 50.7959 57.8962Z"></path>
                <path d="M52.1623 37.7708H52.8622V37.1486H51.729V39.0154H52.1623V37.7708Z"></path>
                <path d="M54.1618 37.7708H54.8617V37.1486H53.7285V39.0154H54.1618V37.7708Z"></path>
                <path d="M54.4619 51.6899H55.2285V44.8777H56.9282V43.6332H54.4619V51.6899Z"></path>
                <path d="M24 24.13V66.87H43.0303V55.5546H47.6629V66.87H48.4294V54.1463H56.1615V66.87H56.9281V55.5546H61.5607V66.87H67.493V24.13H24ZM52.2621 29.0426H52.3288L56.7948 31.7282H47.7962L52.2621 29.0426ZM47.7295 32.2522H56.8614L57.328 35.4782H47.2629L47.7295 32.2522ZM60.5941 54.8013H56.9281V53.393H47.6629V54.8013H43.9968V51.6572H44.63V41.3734H45.6299V36.035H58.9611V41.3734H59.9609V51.6572H60.5941V54.8013Z"></path>
                <path d="M46.8631 42.487H57.728L58.1946 41.7992H46.3965L46.8631 42.487Z"></path>
                <path d="M220.477 24.2292L226.944 35.4296V42.7654H230.786V35.4623L237.22 24.2292H233.182L230.359 29.4364C229.899 30.2224 229.374 31.2049 228.98 32.0564H228.947C228.52 31.2049 228.028 30.1896 227.568 29.4037L224.745 24.2292H220.477ZM203.964 27.4059H209.512V42.7654H213.386V27.4059H218.934V24.2292H203.964V27.4059ZM197.168 42.7654H201.009V24.2292H197.168V42.7654ZM183.576 65.2643C184.922 66.1485 186.826 67 189.486 67C193.425 67 196.314 64.8385 196.314 61.3998C196.314 55.5704 188.304 55.9634 188.304 52.7867C188.304 51.6733 189.387 51.1165 190.766 51.1165C192.309 51.1165 193.622 51.6078 195.198 52.4593L195.264 49.0861C194.213 48.4966 192.539 47.9726 190.602 47.9726C186.498 47.9726 184.266 50.4615 184.266 53.278C184.266 54.719 184.725 55.7669 185.448 56.5857C187.713 59.1401 192.243 59.5659 192.243 62.0221C192.243 63.3976 190.733 63.8888 189.321 63.8888C187.647 63.8888 186.268 63.2011 185.054 62.4478L183.576 65.2643ZM181.114 41.2917C182.427 42.1759 184.364 43.0274 187.023 43.0274C190.963 43.0274 193.852 40.9642 193.852 37.3945C193.852 31.6961 185.809 31.9254 185.809 28.7814C185.809 27.7007 186.925 27.1112 188.271 27.1112C189.814 27.1112 191.16 27.6352 192.736 28.4867L192.801 25.1135C191.718 24.4912 190.044 24 188.107 24C184.003 24 181.803 26.489 181.803 29.3054C181.803 30.7464 182.23 31.7944 182.952 32.6131C185.251 35.1676 189.748 35.5605 189.748 38.0168C189.748 39.425 188.238 39.8835 186.859 39.8835C185.185 39.8835 183.773 39.2285 182.591 38.4752L181.114 41.2917ZM169.459 63.5286V51.4113H171.823C175.631 51.4113 177.962 53.7692 177.962 57.4699C177.962 61.1051 175.631 63.5286 171.823 63.5286H169.459ZM169.427 32.7768V27.4059H171.068C172.907 27.4059 173.957 28.4867 173.957 29.9604C173.957 31.5651 172.71 32.7768 171.068 32.7768H169.427ZM165.585 42.7654H169.427V35.5933H170.904C171.561 35.8553 172.677 37.9513 175.5 42.7654H179.932C177.109 38.0168 175.336 34.8728 174.384 34.3816V34.3488C176.091 33.5628 177.765 31.9581 177.765 29.5347C177.765 26.6855 175.96 24.2292 171.002 24.2292H165.585V42.7654ZM165.585 66.738H171.757C177.798 66.738 182 62.9391 182 57.3717C182 51.9353 178.126 48.2018 171.823 48.2018H165.585V66.738ZM151.436 42.7654H162.237V39.5887H155.31V34.8728H161.252V31.6961H155.31V27.4059H162.171V24.2292H151.436V42.7654ZM151.436 66.738H162.237V63.5613H155.277V58.8781H161.252V55.6687H155.277V51.3785H162.171V48.2018H151.436V66.738ZM137.319 66.738H148.12V63.5613H141.193V58.8781H147.135V55.6687H141.193V51.3785H148.054V48.2018H137.319V66.738ZM130.458 24.2292L138.173 42.8964H141.226L149.171 24.2292H145.231L141.16 34.1196C140.7 35.2986 140.274 36.4448 139.913 37.5583H139.847C139.453 36.4448 139.059 35.3313 138.567 34.1196L134.693 24.2292H130.458ZM124.318 42.7654H128.192V24.2292H124.318V42.7654ZM124.318 66.738H134.66V63.5613H128.192V48.2018H124.318V66.738ZM105.441 66.738H109.282V58.9436H115.028V55.7669H109.282V51.3785H116.045V48.2018H105.441V66.738ZM102.913 42.7654H106.754V32.6131C106.754 31.7616 106.754 30.6154 106.689 30.0259H106.754C107.247 30.8446 107.805 31.5651 108.527 32.4821L116.571 42.7654H120.182V24.2292H116.341V33.6611C116.341 34.4471 116.341 35.6588 116.374 36.543H116.341C115.815 35.757 115.323 35.0693 114.568 34.1196L106.787 24.2292H102.913V42.7654ZM86.5312 57.4699C86.5312 53.6382 89.0591 51.1493 92.4078 51.1493C95.7564 51.1493 98.2515 53.6382 98.2515 57.4699C98.2515 61.3343 95.7564 63.8233 92.4078 63.8233C89.0591 63.8233 86.5312 61.3343 86.5312 57.4699ZM83.0841 35.5278C83.0841 40.4402 85.809 43.0274 90.9961 43.0274C95.9862 43.0274 99.1051 40.5057 99.1051 35.4623V24.2292H95.2311V35.4296C95.2311 38.2788 93.8195 39.7852 91.0618 39.7852C88.4682 39.7852 86.958 38.2133 86.958 35.4296V24.2292H83.0841V35.5278ZM92.4078 67C98.0545 67 102.29 62.9718 102.29 57.4699C102.29 52.0008 98.0545 47.9398 92.4078 47.9398C86.7282 47.9398 82.4932 52.0008 82.4932 57.4699C82.4932 62.9718 86.7282 67 92.4078 67Z"></path>
            </svg>
            <span class="ds2-top-nav__visually-hidden">University of Leeds homepage</span>
        </a>

        <div class="ds2-top-nav__search-group">
            <button class="ds2-top-nav__search-close" type="button">
                <span class="ds2-top-nav__back-arrow" aria-hidden="true"></span>
                <span class="ds2-top-nav__visually-hidden">Close search</span>
            </button>

            <form class="ds2-top-nav__search" id="ds2-top-nav-search" role="search" action="https://www.leeds.ac.uk/search" method="get">
                <label class="ds2-top-nav__search-label ds2-top-nav__visually-hidden" for="ds2-top-nav-search-input">Search leeds.ac.uk</label>
                <input class="ds2-top-nav__search-input" id="ds2-top-nav-search-input" name="q" type="search" placeholder="Search">

                <input type="hidden" name="searchOption" value="searchSite">

                <button class="ds2-top-nav__search-submit" type="submit">
                    <svg class="ds2-top-nav__search-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" aria-hidden="true" focusable="false">
                        <path d="M20.67,18.67H19.61l-.37-.36a8.66,8.66,0,1,0-.93.93l.36.37v1.06l6.66,6.65,2-2Zm-8,0a6,6,0,1,1,6-6A6,6,0,0,1,12.67,18.67Z"></path>
                    </svg>
                    <span class="ds2-top-nav__visually-hidden">Search</span>
                </button>
            </form>
        </div>

        <button class="ds2-top-nav__search-toggle" type="button" aria-controls="ds2-top-nav-search">
            <svg class="ds2-top-nav__search-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" aria-hidden="true" focusable="false">
                <path d="M20.67,18.67H19.61l-.37-.36a8.66,8.66,0,1,0-.93.93l.36.37v1.06l6.66,6.65,2-2Zm-8,0a6,6,0,1,1,6-6A6,6,0,0,1,12.67,18.67Z"></path>
            </svg>
            <span class="ds2-top-nav__visually-hidden">Search</span>
        </button>

        <button class="ds2-top-nav__menu-toggle" type="button" aria-controls="ds2-top-nav-menu">
            <svg class="ds2-top-nav__menu-icon ds2-top-nav__menu-icon--burger" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 12" aria-hidden="true" focusable="false">
                <rect x="0" y="0" width="14" height="2"></rect>
                <rect x="9" y="5" width="15" height="2"></rect>
                <rect x="1" y="10" width="16" height="2"></rect>
            </svg>
            <svg class="ds2-top-nav__menu-icon ds2-top-nav__menu-icon--close" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true" focusable="false">
                <path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"></path>
            </svg>
            <span class="ds2-top-nav__visually-hidden">Menu</span>
        </button>
    </div>

    <nav class="ds2-top-nav__nav" id="ds2-top-nav-menu" aria-label="Main">
        <ul class="ds2-top-nav__list ds2-top-nav__list--level-1">

            <li class="ds2-top-nav__item ds2-top-nav__item--level-1 ds2-top-nav__item--has-children">
                <button class="ds2-top-nav__link ds2-top-nav__link--button" type="button">
                    <span class="ds2-top-nav__link-text">Study</span>
                    <span class="ds2-top-nav__arrow" aria-hidden="true"></span>
                </button>

                <div class="ds2-top-nav__panel ds2-top-nav__panel--level-2">
                    <button class="ds2-top-nav__back" type="button">
                        <span class="ds2-top-nav__back-arrow" aria-hidden="true"></span>
                        <span class="ds2-top-nav__visually-hidden">Back to main menu</span>
                    </button>
                    <ul class="ds2-top-nav__list ds2-top-nav__list--level-2">

                        <li class="ds2-top-nav__item ds2-top-nav__item--level-2">
                            <a class="ds2-top-nav__link" href="#">Course search</a>
                        </li>

                        <li class="ds2-top-nav__item ds2-top-nav__item--level-2 ds2-top-nav__item--has-children">
                            <button class="ds2-top-nav__link ds2-top-nav__link--button" type="button">
                                <span class="ds2-top-nav__link-text">Undergraduate</span>
                                <span class="ds2-top-nav__arrow" aria-hidden="true"></span>
                            </button>

                            <div class="ds2-top-nav__panel ds2-top-nav__panel--level-3">
                                <button class="ds2-top-nav__back" type="button">
                                    <span class="ds2-top-nav__back-arrow" aria-hidden="true"></span>
                                    <span class="ds2-top-nav__visually-hidden">Back to Study</span>
                                </button>
                                <ul class="ds2-top-nav__list ds2-top-nav__list--level-3">

                                    <li class="ds2-top-nav__item ds2-top-nav__item--level-3">
                                        <a class="ds2-top-nav__link" href="#">Undergraduate home page</a>
                                    </li>

                                    <li class="ds2-top-nav__item ds2-top-nav__item--level-3">
                                        <a class="ds2-top-nav__link" href="#">Clearing 2026</a>
                                    </li>

                                    <li class="ds2-top-nav__item ds2-top-nav__item--level-3">
                                        <a class="ds2-top-nav__link" href="#">Undergraduate course search</a>
                                    </li>

                                    <li class="ds2-top-nav__item ds2-top-nav__item--level-3">
                                        <a class="ds2-top-nav__link" href="#">Fees and funding</a>
                                    </li>

                                    <li class="ds2-top-nav__item ds2-top-nav__item--level-3">
                                        <a class="ds2-top-nav__link" href="#">Student life</a>
                                    </li>

                                </ul>
                            </div>
                        </li>

                        <li class="ds2-top-nav__item ds2-top-nav__item--level-2 ds2-top-nav__item--has-children">
                            <button class="ds2-top-nav__link ds2-top-nav__link--button" type="button">
                                <span class="ds2-top-nav__link-text">Online courses</span>
                                <span class="ds2-top-nav__arrow" aria-hidden="true"></span>
                            </button>

                            <div class="ds2-top-nav__panel ds2-top-nav__panel--level-3">
                                <button class="ds2-top-nav__back" type="button">
                                    <span class="ds2-top-nav__back-arrow" aria-hidden="true"></span>
                                    <span class="ds2-top-nav__visually-hidden">Back to Study</span>
                                </button>
                                <ul class="ds2-top-nav__list ds2-top-nav__list--level-3">

                                    <li class="ds2-top-nav__item ds2-top-nav__item--level-3">
                                        <a class="ds2-top-nav__link" href="#">Overview</a>
                                    </li>

                                    <li class="ds2-top-nav__item ds2-top-nav__item--level-3">
                                        <a class="ds2-top-nav__link" href="#">Online Masters study</a>
                                    </li>

                                    <li class="ds2-top-nav__item ds2-top-nav__item--level-3">
                                        <a class="ds2-top-nav__link" href="#">Short online courses</a>
                                    </li>

                                    <li class="ds2-top-nav__item ds2-top-nav__item--level-3">
                                        <a class="ds2-top-nav__link" href="#">PhD by distance learning</a>
                                    </li>

                                </ul>
                            </div>
                        </li>

                        <li class="ds2-top-nav__item ds2-top-nav__item--level-2 ds2-top-nav__item--has-children">
                            <button class="ds2-top-nav__link ds2-top-nav__link--button" type="button">
                                <span class="ds2-top-nav__link-text">Masters</span>
                                <span class="ds2-top-nav__arrow" aria-hidden="true"></span>
                            </button>

                            <div class="ds2-top-nav__panel ds2-top-nav__panel--level-3">
                                <button class="ds2-top-nav__back" type="button">
                                    <span class="ds2-top-nav__back-arrow" aria-hidden="true"></span>
                                    <span class="ds2-top-nav__visually-hidden">Back to Study</span>
                                </button>
                                <ul class="ds2-top-nav__list ds2-top-nav__list--level-3">

                                    <li class="ds2-top-nav__item ds2-top-nav__item--level-3">
                                        <a class="ds2-top-nav__link" href="#">Masters home page</a>
                                    </li>

                                    <li class="ds2-top-nav__item ds2-top-nav__item--level-3">
                                        <a class="ds2-top-nav__link" href="#">Postgraduate open day</a>
                                    </li>

                                    <li class="ds2-top-nav__item ds2-top-nav__item--level-3">
                                        <a class="ds2-top-nav__link" href="#">Masters course search</a>
                                    </li>

                                    <li class="ds2-top-nav__item ds2-top-nav__item--level-3">
                                        <a class="ds2-top-nav__link" href="#">Applying</a>
                                    </li>

                                    <li class="ds2-top-nav__item ds2-top-nav__item--level-3">
                                        <a class="ds2-top-nav__link" href="#">Fees and funding</a>
                                    </li>

                                </ul>
                            </div>
                        </li>

                        <li class="ds2-top-nav__item ds2-top-nav__item--level-2">
                            <a class="ds2-top-nav__link" href="#">Research degrees</a>
                        </li>

                        <li class="ds2-top-nav__item ds2-top-nav__item--level-2">
                            <a class="ds2-top-nav__link" href="#">International students</a>
                        </li>

                        <li class="ds2-top-nav__item ds2-top-nav__item--level-2">
                            <a class="ds2-top-nav__link" href="#">Foundation courses</a>
                        </li>

                        <li class="ds2-top-nav__item ds2-top-nav__item--level-2">
                            <a class="ds2-top-nav__link" href="#">Mature students</a>
                        </li>

                    </ul>
                </div>
            </li>

            <li class="ds2-top-nav__item ds2-top-nav__item--level-1 ds2-top-nav__item--has-children">
                <button class="ds2-top-nav__link ds2-top-nav__link--button" type="button">
                    <span class="ds2-top-nav__link-text">Faculties</span>
                    <span class="ds2-top-nav__arrow" aria-hidden="true"></span>
                </button>

                <div class="ds2-top-nav__panel ds2-top-nav__panel--level-2">
                    <button class="ds2-top-nav__back" type="button">
                        <span class="ds2-top-nav__back-arrow" aria-hidden="true"></span>
                        <span class="ds2-top-nav__visually-hidden">Back to main menu</span>
                    </button>
                    <ul class="ds2-top-nav__list ds2-top-nav__list--level-2">

                        <li class="ds2-top-nav__item ds2-top-nav__item--level-2">
                            <a class="ds2-top-nav__link" href="#">Arts, Humanities and Cultures</a>
                        </li>

                        <li class="ds2-top-nav__item ds2-top-nav__item--level-2">
                            <a class="ds2-top-nav__link" href="#">Biological Sciences</a>
                        </li>

                        <li class="ds2-top-nav__item ds2-top-nav__item--level-2">
                            <a class="ds2-top-nav__link" href="#">Business School</a>
                        </li>

                        <li class="ds2-top-nav__item ds2-top-nav__item--level-2">
                            <a class="ds2-top-nav__link" href="#">Engineering and Physical Sciences</a>
                        </li>

                        <li class="ds2-top-nav__item ds2-top-nav__item--level-2">
                            <a class="ds2-top-nav__link" href="#">Environment</a>
                        </li>

                        <li class="ds2-top-nav__item ds2-top-nav__item--level-2">
                            <a class="ds2-top-nav__link" href="#">Medicine and Health</a>
                        </li>

                        <li class="ds2-top-nav__item ds2-top-nav__item--level-2">
                            <a class="ds2-top-nav__link" href="#">Social Sciences</a>
                        </li>

                        <li class="ds2-top-nav__item ds2-top-nav__item--level-2">
                            <a class="ds2-top-nav__link" href="#">Faculty contacts</a>
                        </li>

                    </ul>
                </div>
            </li>

            <li class="ds2-top-nav__item ds2-top-nav__item--level-1 ds2-top-nav__item--has-children">
                <button class="ds2-top-nav__link ds2-top-nav__link--button" type="button">
                    <span class="ds2-top-nav__link-text">Research</span>
                    <span class="ds2-top-nav__arrow" aria-hidden="true"></span>
                </button>

                <div class="ds2-top-nav__panel ds2-top-nav__panel--level-2">
                    <button class="ds2-top-nav__back" type="button">
                        <span class="ds2-top-nav__back-arrow" aria-hidden="true"></span>
                        <span class="ds2-top-nav__visually-hidden">Back to main menu</span>
                    </button>
                    <ul class="ds2-top-nav__list ds2-top-nav__list--level-2">

                        <li class="ds2-top-nav__item ds2-top-nav__item--level-2">
                            <a class="ds2-top-nav__link" href="#">Research overview</a>
                        </li>

                        <li class="ds2-top-nav__item ds2-top-nav__item--level-2">
                            <a class="ds2-top-nav__link" href="#">Research excellence and impact</a>
                        </li>

                        <li class="ds2-top-nav__item ds2-top-nav__item--level-2">
                            <a class="ds2-top-nav__link" href="#">Strategy and environment</a>
                        </li>

                        <li class="ds2-top-nav__item ds2-top-nav__item--level-2">
                            <a class="ds2-top-nav__link" href="#">Researcher and staff development</a>
                        </li>

                        <li class="ds2-top-nav__item ds2-top-nav__item--level-2">
                            <a class="ds2-top-nav__link" href="#">Public engagement</a>
                        </li>

                        <li class="ds2-top-nav__item ds2-top-nav__item--level-2">
                            <a class="ds2-top-nav__link" href="#">Research and innovation contacts</a>
                        </li>

                    </ul>
                </div>
            </li>

            <li class="ds2-top-nav__item ds2-top-nav__item--level-1 ds2-top-nav__item--has-children">
                <button class="ds2-top-nav__link ds2-top-nav__link--button" type="button">
                    <span class="ds2-top-nav__link-text">Business</span>
                    <span class="ds2-top-nav__arrow" aria-hidden="true"></span>
                </button>

                <div class="ds2-top-nav__panel ds2-top-nav__panel--level-2">
                    <button class="ds2-top-nav__back" type="button">
                        <span class="ds2-top-nav__back-arrow" aria-hidden="true"></span>
                        <span class="ds2-top-nav__visually-hidden">Back to main menu</span>
                    </button>
                    <ul class="ds2-top-nav__list ds2-top-nav__list--level-2">

                        <li class="ds2-top-nav__item ds2-top-nav__item--level-2">
                            <a class="ds2-top-nav__link" href="#">Overview</a>
                        </li>

                        <li class="ds2-top-nav__item ds2-top-nav__item--level-2">
                            <a class="ds2-top-nav__link" href="#">Access our expertise</a>
                        </li>

                        <li class="ds2-top-nav__item ds2-top-nav__item--level-2">
                            <a class="ds2-top-nav__link" href="#">Training and development</a>
                        </li>

                        <li class="ds2-top-nav__item ds2-top-nav__item--level-2">
                            <a class="ds2-top-nav__link" href="#">Recruit our students and graduates</a>
                        </li>

                        <li class="ds2-top-nav__item ds2-top-nav__item--level-2">
                            <a class="ds2-top-nav__link" href="#">Use our facilities</a>
                        </li>

                        <li class="ds2-top-nav__item ds2-top-nav__item--level-2">
                            <a class="ds2-top-nav__link" href="#">Support for small businesses</a>
                        </li>

                        <li class="ds2-top-nav__item ds2-top-nav__item--level-2">
                            <a class="ds2-top-nav__link" href="#">Our business partners</a>
                        </li>

                    </ul>
                </div>
            </li>

            <li class="ds2-top-nav__item ds2-top-nav__item--level-1 ds2-top-nav__item--has-children">
                <button class="ds2-top-nav__link ds2-top-nav__link--button" type="button">
                    <span class="ds2-top-nav__link-text">About</span>
                    <span class="ds2-top-nav__arrow" aria-hidden="true"></span>
                </button>

                <div class="ds2-top-nav__panel ds2-top-nav__panel--level-2">
                    <button class="ds2-top-nav__back" type="button">
                        <span class="ds2-top-nav__back-arrow" aria-hidden="true"></span>
                        <span class="ds2-top-nav__visually-hidden">Back to main menu</span>
                    </button>
                    <ul class="ds2-top-nav__list ds2-top-nav__list--level-2">

                        <li class="ds2-top-nav__item ds2-top-nav__item--level-2">
                            <a class="ds2-top-nav__link" href="#">Campus map</a>
                        </li>

                        <li class="ds2-top-nav__item ds2-top-nav__item--level-2">
                            <a class="ds2-top-nav__link" href="#">Our history</a>
                        </li>

                        <li class="ds2-top-nav__item ds2-top-nav__item--level-2">
                            <a class="ds2-top-nav__link" href="#">Faculties</a>
                        </li>

                        <li class="ds2-top-nav__item ds2-top-nav__item--level-2">
                            <a class="ds2-top-nav__link" href="#">Governance</a>
                        </li>

                        <li class="ds2-top-nav__item ds2-top-nav__item--level-2">
                            <a class="ds2-top-nav__link" href="#">Reports, accounts and policies</a>
                        </li>

                        <li class="ds2-top-nav__item ds2-top-nav__item--level-2">
                            <a class="ds2-top-nav__link" href="#">Strategies and values</a>
                        </li>

                        <li class="ds2-top-nav__item ds2-top-nav__item--level-2">
                            <a class="ds2-top-nav__link" href="#">Equality</a>
                        </li>

                        <li class="ds2-top-nav__item ds2-top-nav__item--level-2">
                            <a class="ds2-top-nav__link" href="#">Executive team</a>
                        </li>

                        <li class="ds2-top-nav__item ds2-top-nav__item--level-2">
                            <a class="ds2-top-nav__link" href="#">News</a>
                        </li>

                        <li class="ds2-top-nav__item ds2-top-nav__item--level-2">
                            <a class="ds2-top-nav__link" href="#">Events</a>
                        </li>

                    </ul>
                </div>
            </li>

            <li class="ds2-top-nav__item ds2-top-nav__item--level-1">
                <a class="ds2-top-nav__link" href="#">Alumni</a>
            </li>

        </ul>
    </nav>
</header>
  • Content:
    // A mixin, so this file outputs nothing when the library wildcard imports it on its own.
    // _ds2-top-nav.scss includes it at the end of the block, after the mobile rules.
    @mixin ds2-top-nav-desktop {
      @media (min-width: $topNavDesktopBreakpoint) {
        &--js {
          // Content sits in a centred column up to $topNavDesktopMaxWidth, at least $topNavLogoInset
          // from each side.
          $contentInset: unquote("max(#{$topNavLogoInset}, calc((100% - #{$topNavDesktopMaxWidth}) / 2))");
    
          // Equal side columns keep the links centred on the page.
          display: grid;
          // next line disabled to help options be more centralised
          // grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
          column-gap: $spacing-4;
          align-items: center;
          min-height: $topNavDesktopBarHeight;
          padding-inline: $contentInset;
    
          .ds2-top-nav__bar {
            display: contents;
          }
    
          .ds2-top-nav__logo {
            grid-column: 1;
            grid-row: 1;
            justify-self: start;
            margin: 0;
            padding: 0;
          }
    
          .ds2-top-nav__menu-toggle {
            display: none;
          }
    
          // The search icon sits at the right.
          .ds2-top-nav__search-toggle {
            grid-column: 3;
            grid-row: 1;
            justify-self: end;
          }
    
          .ds2-top-nav__search {
            display: none;
            grid-column: 3;
            grid-row: 1;
            justify-self: end;
            width: 15.625rem;
            max-width: none;
            height: 2.75rem;
            margin: 0;
          }
    
          // Level 1 links in a row.
          .ds2-top-nav__nav {
            display: block;
            position: static;
            grid-column: 2;
            grid-row: 1;
            justify-self: center;
            min-height: 0;
            padding: 0;
            background: none;
            overflow: visible;
          }
    
          .ds2-top-nav__list--level-1 {
            flex-direction: row;
            gap: 0;
            max-width: none;
            padding: 0;
    
            > .ds2-top-nav__item > .ds2-top-nav__link {
              width: auto;
              // Full bar height, so the dropdown starts right below the bar.
              min-height: $topNavDesktopBarHeight;
              padding: 0 $topNavDesktopLinkPaddingFluid;
    
              &[aria-expanded="true"] {
                color: $topNavHighlightColour;
              }
    
              // Points down when closed and up when open.
              > .ds2-top-nav__arrow {
                transform: rotate(90deg);
                transition: transform 0.2s ease;
    
                @media (prefers-reduced-motion: reduce) {
                  transition: none;
                }
              }
    
              &[aria-expanded="true"] > .ds2-top-nav__arrow {
                transform: rotate(-90deg);
              }
            }
          }
    
          // No back buttons on desktop, as levels 2 and 3 show side by side.
          .ds2-top-nav__back {
            display: none;
          }
    
          // Level 2: a full-width dropdown below the bar.
          .ds2-top-nav__panel--level-2 {
            display: none;
            position: absolute;
            z-index: $topNavMenuZIndex;
            top: 100%;
            left: 0;
            right: 0;
            width: auto;
            height: auto;
            min-height: 0;
            overflow: visible;
            padding: $spacing-3 $contentInset $spacing-7;
            transform: none;
            visibility: visible;
            transition: none;
    
            // Gives the dropdown an edge in high contrast mode.
            // In px so the edge stays a hairline.
            @media (forced-colors: active) {
              border-bottom: 1px solid CanvasText;
            }
          }
    
          // Level 3 sits beside the level 2 column.
          .ds2-top-nav__panel--level-3 {
            display: none;
            position: absolute;
            top: 0;
            left: 100%;
            width: 100%;
            height: auto;
            min-height: 0;
            overflow: visible;
            padding: 0;
            background: none;
            transform: none;
            visibility: visible;
            transition: none;
          }
    
          .ds2-top-nav__panel--open {
            display: block;
          }
    
          .ds2-top-nav__list--level-2 {
            position: relative;
            gap: 0;
            width: 33%;
            max-width: none;
            padding: 0;
          }
    
          .ds2-top-nav__list--level-3 {
            gap: 0;
            max-width: none;
            padding: 0;
          }
    
          .ds2-top-nav__panel .ds2-top-nav__link {
            min-height: 3.25rem;
            padding: 0;
    
            &:focus-visible {
              outline-offset: $topNavFocusOutlineOffset;
            }
    
            &[aria-expanded="true"] {
              color: $topNavHighlightColour;
            }
          }
    
          // Level 2 arrows grow across to the level 3 column while it's open.
          .ds2-top-nav__list--level-2 > .ds2-top-nav__item > .ds2-top-nav__link {
            padding-right: $spacing-5;
    
            .ds2-top-nav__link-text {
              flex: none;
            }
    
            > .ds2-top-nav__arrow {
              flex: 0 1 1.25rem;
              color: $topNavHighlightColour;
              transition: flex-grow 0.4s ease;
    
              @media (prefers-reduced-motion: reduce) {
                transition: none;
              }
            }
    
            &[aria-expanded="true"] > .ds2-top-nav__arrow {
              flex-grow: 1;
            }
          }
    
          // While a level 2 item is open, the others fade back so it and its links stand out.
          // Hover and focus bring a link back to full strength, so the focus ring isn't faded.
          .ds2-top-nav__list--level-2 > .ds2-top-nav__item > .ds2-top-nav__link {
            transition: opacity 0.2s ease;
    
            @media (prefers-reduced-motion: reduce) {
              transition: none;
            }
          }
    
          .ds2-top-nav__list--level-2:has(.ds2-top-nav__item > [aria-expanded="true"])
            > .ds2-top-nav__item
            > .ds2-top-nav__link:not([aria-expanded="true"]):not(:hover):not(:focus-visible) {
            opacity: 0.6;
    
            // High contrast mode is there to maximise contrast, so nothing fades.
            @media (forced-colors: active) {
              opacity: 1;
            }
          }
        }
    
        // Desktop search open: the box takes the place of the nav links.
        &--search-open,
        &--search-closing {
          // Columns: logo, then search.
          grid-template-columns: auto minmax(0, 1fr);
    
          .ds2-top-nav__search-toggle {
            display: none;
          }
    
          // The logo stays on desktop.
          .ds2-top-nav__logo {
            display: inline-flex;
          }
    
          .ds2-top-nav__nav {
            display: none;
          }
    
          // Packed to the right, so the box opens leftwards from the icon with the arrow on its edge.
          .ds2-top-nav__search-group {
            display: flex;
            align-items: center;
            justify-content: flex-end;
            gap: $spacing-4;
            grid-column: 2;
            grid-row: 1;
          }
    
          .ds2-top-nav__search-close {
            grid-column: auto;
            justify-self: auto;
          }
    
          .ds2-top-nav__search {
            display: flex;
            flex: 1;
            width: auto;
            max-width: $topNavDesktopSearchMaxWidth;
            justify-self: auto;
            margin-inline: 0;
          }
        }
    
        &--search-open .ds2-top-nav__search {
          animation: ds2-top-nav-desktop-search-grow $topNavSearchSlideDuration ease;
    
          @media (prefers-reduced-motion: reduce) {
            animation: none;
          }
        }
    
        &--search-closing .ds2-top-nav__search {
          animation: ds2-top-nav-desktop-search-shrink $topNavSearchSlideDuration ease forwards;
        }
    
        // On desktop the logo and icon stay in the grid while search animates.
        &--search-opening,
        &--search-closing {
          .ds2-top-nav__logo,
          .ds2-top-nav__search-toggle {
            position: static;
            inset: auto;
            margin-block: 0;
            pointer-events: auto;
          }
    
          .ds2-top-nav__logo {
            animation: none;
          }
        }
    
        // Closing: the icon is back straight away so it can take focus, but only fades in at the end,
        // as the box collapses into it.
        &--search-closing {
          .ds2-top-nav__search-toggle {
            display: inline-flex;
            grid-column: 2;
            grid-row: 1;
            justify-self: end;
            animation: ds2-top-nav-icon-fade-in-late $topNavSearchSlideDuration ease;
          }
        }
    
        &--search-opening {
          .ds2-top-nav__search-toggle {
            display: none;
          }
        }
      }
    }
    
  • URL: /components/raw/ds2-top-nav/_top-nav-desktop.scss
  • Filesystem Path: src/library/ds2/02-components/top-nav/_top-nav-desktop.scss
  • Size: 8.2 KB
  • Content:
    $topNavBgColour: #000;
    $topNavFgColour: #fff;
    $topNavHighlightColour: #00D0A0;
    $topNavSearchBorderColour: #999;
    
    // Must match desktopMediaQuery in the JS. In rem so larger browser text keeps the mobile menu
    // until the screen is wide enough.
    $topNavDesktopBreakpoint: 80rem;
    // From here up the logo stays visible while search is open. In rem, like the desktop one.
    $topNavTabletBreakpoint: 48rem;
    
    // Top row.
    $topNavCrestWidth: 2.718125rem;
    $topNavCrestHeight: 2.67125rem;
    // From the left edge of the screen to the logo, on mobile and desktop. On desktop it's the
    // smallest inset, so wide screens keep the centred column.
    $topNavLogoInset: 2.375rem; // 38px
    $topNavBarHeight: 7.75rem; // 124px
    // Tops up the bar's padding so the logo sits in the middle of the bar.
    $topNavLogoPaddingBlock: ($topNavBarHeight - $topNavCrestHeight) * 0.5 - $spacing-5;
    
    // Icon buttons stay px so they don't crowd the bar with larger text. They have no text to grow.
    $topNavIconButtonSize: 40px;
    $topNavMenuButtonSize: 48px;
    $topNavSearchIconSize: 20px;
    $topNavCloseIconSize: 26px;
    
    // Menu rows.
    $topNavLinkFontSize: 1rem;
    $topNavRowHeight: 3.75rem;
    $topNavLinkPaddingInline: 2.25rem;
    
    $topNavChildRowHeight: 3.5rem;
    $topNavBackHeight: 3.75rem;
    // Must match SLIDE in ref/mobile-menu-checks.js.
    $topNavSlideDuration: 0.6s;
    
    // Caps that stop the search box and menu stretching on tablet.
    $topNavSearchMaxWidth: 30rem;
    $topNavListMaxWidth: 30rem;
    
    // Search open and close animation. Must match searchSlideDuration in the JS.
    $topNavSearchSlideDuration: 0.4s;
    // Everything else in the row with the open box: padding, close arrow, margins and burger.
    $topNavSearchRowOthers: "#{$spacing-5} * 2 + #{$topNavIconButtonSize} + #{$spacing-3} * 2 + #{$topNavMenuButtonSize}";
    // The widest the open box gets below desktop.
    $topNavSearchMaxWidthOpen: 33.125rem;
    $topNavSearchSlideEndWidth: unquote("min(#{$topNavSearchMaxWidthOpen}, calc(100vw - (#{$topNavSearchRowOthers})))");
    // From tablet up the logo stays in the row, so the open box leaves room for it too.
    // The logo's padding is the inset less the bar's, plus $spacing-5 on its right.
    $topNavLogoWidth: "#{$topNavCrestWidth} + #{$topNavLogoInset}";
    $topNavTabletSearchSlideEndWidth: unquote("min(#{$topNavSearchMaxWidthOpen}, calc(100vw - (#{$topNavSearchRowOthers} + #{$topNavLogoWidth})))");
    
    // Arrows stay px: the stroke is a border, and Sass sums these for the arrowhead.
    $topNavArrowStroke: 2px;
    $topNavArrowHeadSize: 8px;
    
    // Desktop. The bar is the same height as on mobile.
    $topNavDesktopBarHeight: $topNavBarHeight;
    $topNavDesktopMaxWidth: 87.5rem;
    // Narrows towards 1280px so the links still fit with larger text spacing.
    // In px because it follows the screen width, not the text.
    $topNavDesktopLinkPaddingFluid: unquote("clamp(10px, calc(2.5vw - 20px), #{$spacing-4})");
    // The open box fills the space the links leave, up to this width.
    $topNavDesktopSearchMaxWidth: 50rem;
    
    // Outlines stay px so they stay crisp.
    $topNavFocusOutlineWidth: 2px;
    $topNavFocusOutlineOffset: 2px;
    $topNavMenuZIndex: 100;
    // A hairline, so it stays px and mustn't grow.
    $topNavSeamOverlap: 1px;
    
  • URL: /components/raw/ds2-top-nav/_top-nav-vars.scss
  • Filesystem Path: src/library/ds2/02-components/top-nav/_top-nav-vars.scss
  • Size: 3.1 KB
  • Content:
    // The variables and the desktop mixin. Neither outputs anything on its own.
    @import 'top-nav-vars';
    @import 'top-nav-desktop';
    
    .ds2-top-nav {
      position: relative;
      font-family: uolInter, Arial, Helvetica, sans-serif;
      background: $topNavBgColour;
      color: $topNavFgColour;
    
      // Keeps sizing consistent, as the site doesn't set border-box globally.
      &,
      *,
      *::before,
      *::after {
        box-sizing: border-box;
      }
    
      // One keyboard focus ring for the whole header. :where() keeps it easy to override.
      :where(a, button):focus-visible {
        outline: $topNavFocusOutlineWidth solid $topNavHighlightColour;
        outline-offset: $topNavFocusOutlineOffset;
      }
    
      // Top row.
    
      &__bar {
        display: flex;
        align-items: center;
        position: relative;
        // Keeps the bar's height when the search box hides the logo.
        min-height: $topNavBarHeight;
        padding: $spacing-5;
      }
    
      &__logo {
        display: inline-flex;
        align-items: center;
        margin-right: auto;
        padding-block: $topNavLogoPaddingBlock;
        // Tops up the bar's padding to $topNavLogoInset. Shrinks on very narrow screens with large
        // text, so the bar still fits.
        padding-inline: unquote("min(#{$topNavLogoInset} - #{$spacing-5}, 8.6vw) min(#{$spacing-5}, 8.6vw)");
        color: inherit;
        fill: currentColor;
      }
    
      // Mobile and tablet show the crest alone; desktop swaps it for the full logo.
      &__logo-crest {
        width: $topNavCrestWidth;
        height: $topNavCrestHeight;
    
        @media (min-width: $topNavDesktopBreakpoint) {
          display: none;
        }
      }
    
      &__logo-full {
        display: none;
        height: $topNavCrestHeight;
    
        @media (min-width: $topNavDesktopBreakpoint) {
          display: block;
        }
      }
    
      // Shared reset for the icon buttons.
      &__search-toggle,
      &__search-close,
      &__menu-toggle,
      &__search-submit {
        align-items: center;
        justify-content: center;
        flex: none;
        width: $topNavIconButtonSize;
        height: $topNavIconButtonSize;
        padding: 0;
        border: 0;
        background: none;
        color: inherit;
        cursor: pointer;
    
        // Hover colour for mouse users only, so it doesn't stick after a tap.
        @media (hover: hover) {
          &:hover {
            color: $topNavHighlightColour;
          }
        }
      }
    
      &__menu-toggle {
        width: $topNavMenuButtonSize;
        height: $topNavMenuButtonSize;
      }
    
      // Only shown once the JS is running.
      &__search-toggle,
      &__menu-toggle {
        display: none;
      }
    
      // Only shown while the search box is open, so touch users can close it.
      &__search-close {
        display: none;
    
        .ds2-top-nav__back-arrow {
          margin: 0;
        }
      }
    
      &__search-icon {
        width: $topNavSearchIconSize;
        height: $topNavSearchIconSize;
        fill: currentColor;
      }
    
      &__menu-icon {
        fill: currentColor;
    
        &--burger {
          // In px, like the other icon sizes.
          width: 24px;
          height: 12px;
        }
    
        &--close {
          display: none;
          width: $topNavCloseIconSize;
          height: $topNavCloseIconSize;
        }
      }
    
      // The burger turns into a cross when aria-expanded is true, so the icon matches what's announced.
      &__menu-toggle[aria-expanded="true"] {
        .ds2-top-nav__menu-icon--burger {
          display: none;
        }
    
        .ds2-top-nav__menu-icon--close {
          display: block;
        }
      }
    
      // Only used on desktop, where the close arrow and box move together.
      &__search-group {
        display: contents;
      }
    
      // Search box.
    
      &__search {
        display: flex;
        align-items: center;
        flex: 1;
        min-width: 0;
        max-width: $topNavSearchMaxWidth;
        height: 3.75rem;
        margin-inline: $spacing-3;
        // The border stays px so it remains a hairline.
        border: 1px solid $topNavSearchBorderColour;
        border-radius: 0.25rem;
    
        // The focus ring goes round the whole box, not just the input.
        &:has(.ds2-top-nav__search-input:focus-visible) {
          outline: $topNavFocusOutlineWidth solid $topNavHighlightColour;
          outline-offset: $topNavFocusOutlineOffset;
        }
      }
    
      &__search-input {
        flex: 1;
        min-width: 0;
        height: 100%;
        padding: 0 $spacing-3 0 $spacing-1;
        border: 0;
        background: transparent;
        color: inherit;
        font: inherit;
        // Keeps iOS from zooming in on focus; 1rem is 16px unless the user has chosen a larger size.
        font-size: $topNavLinkFontSize;
    
        &::placeholder {
          color: inherit;
          opacity: 1;
        }
    
        &:focus {
          outline: none;
        }
      }
    
      // Comes after the input in the markup but sits on the left, to match the design.
      &__search-submit {
        display: inline-flex;
        order: -1;
        margin-left: $spacing-2;
      }
    
      // Menu.
    
      &__list {
        display: flex;
        flex-direction: column;
        gap: $spacing-1;
        max-width: $topNavListMaxWidth;
        margin: 0;
        padding: 0;
        list-style: none;
      }
    
      &__list--level-1 {
        padding-block: $spacing-7 $spacing-6;
      }
    
      &__link {
        display: flex;
        align-items: center;
        width: 100%;
        // Uses min-height so rows can grow without clipping the text or tap target.
        min-height: $topNavRowHeight;
        padding: 0 $topNavLinkPaddingInline;
        border: 0;
        background: none;
        color: inherit;
        font: inherit;
        font-size: $topNavLinkFontSize;
        line-height: 1.5;
        text-align: left;
        text-decoration: none;
        cursor: pointer;
    
        // The focus ring sits inside the row, so the screen edge doesn't cut it off.
        &:focus-visible {
          outline-offset: -$topNavFocusOutlineWidth * 2;
        }
    
        @media (hover: hover) {
          &:hover {
            color: $topNavHighlightColour;
            text-decoration: underline;
            // Px so the line stays a hairline.
            text-decoration-thickness: 1px;
            text-underline-offset: 4px;
          }
        }
      }
    
      &__panel &__link {
        min-height: $topNavChildRowHeight;
      }
    
      &__list--level-2,
      &__list--level-3 {
        padding-bottom: $spacing-6;
      }
    
      // Back buttons need the JS, so they're hidden until it runs.
      &__back {
        display: none;
        align-items: center;
        height: $topNavBackHeight;
        padding: 0 $spacing-5;
        border: 0;
        background: none;
        color: $topNavHighlightColour;
        cursor: pointer;
    
        // Keeps the focus ring inside the button, away from the screen edge.
        &:focus-visible {
          outline-offset: -$topNavFocusOutlineWidth * 2;
        }
      }
    
      // Arrows are drawn with borders so they stay visible in high contrast mode.
      &__arrow,
      &__back-arrow {
        position: relative;
        flex: none;
        // In px, like the rest of the arrow.
        width: 13px;
        border-top: $topNavArrowStroke solid currentColor;
    
        // The arrowhead is a rotated square with two borders.
        &::after {
          content: '';
          position: absolute;
          top: -($topNavArrowStroke / 2) - ($topNavArrowHeadSize / 2);
          right: $topNavArrowStroke / 2;
          width: $topNavArrowHeadSize;
          height: $topNavArrowHeadSize;
          border-top: $topNavArrowStroke solid currentColor;
          border-right: $topNavArrowStroke solid currentColor;
          transform: rotate(45deg);
        }
      }
    
      &__arrow {
        margin-left: $spacing-4;
      }
    
      // The same arrow, pointing left.
      &__back-arrow {
        transform: scaleX(-1);
      }
    
      // Without JS every level shows as one indented list, with no arrows suggesting a drill-down.
      &:not(.ds2-top-nav--js) {
        .ds2-top-nav__arrow {
          display: none;
        }
    
        .ds2-top-nav__list--level-2,
        .ds2-top-nav__list--level-3 {
          padding-left: $spacing-5;
        }
      }
    
      // With JS, the search box and menu stay hidden behind the icon buttons until opened.
      &--js {
        .ds2-top-nav__search-toggle,
        .ds2-top-nav__menu-toggle {
          display: inline-flex;
        }
    
        .ds2-top-nav__search,
        .ds2-top-nav__nav {
          display: none;
        }
    
        // The open menu fills the screen below the bar.
        // It overlaps the bar by 1px so no gap shows on scaled displays.
        .ds2-top-nav__nav {
          position: absolute;
          top: calc(100% - #{$topNavSeamOverlap});
          left: 0;
          right: 0;
          z-index: $topNavMenuZIndex;
          min-height: unquote("calc(100vh - #{$topNavBarHeight} + #{$topNavSeamOverlap})");
          min-height: unquote("calc(100dvh - #{$topNavBarHeight} + #{$topNavSeamOverlap})");
          padding-top: $topNavSeamOverlap;
          background: $topNavBgColour;
          // Closed panels sit off to the right; this stops them causing sideways scrolling.
          overflow-x: clip;
        }
    
        .ds2-top-nav__back {
          display: inline-flex;
        }
    
        // Drill-down: each panel slides in from the right over its parent list.
        .ds2-top-nav__panel {
          position: absolute;
          // Sits above the arrows in the list below.
          z-index: 1;
          top: 0;
          left: 0;
          width: 100%;
          // Closed panels don't add to the page height.
          height: 100%;
          overflow: hidden;
          padding-top: $spacing-5;
          background: $topNavBgColour;
          transform: translateX(100%);
          // Hidden once closed, so nothing in it can be focused or read out.
          visibility: hidden;
          transition: transform $topNavSlideDuration ease, visibility 0s linear $topNavSlideDuration;
        }
    
        .ds2-top-nav__panel--open {
          height: auto;
          min-height: 100%;
          overflow: visible;
          transform: none;
          visibility: visible;
          transition: transform $topNavSlideDuration ease;
        }
    
        @media (prefers-reduced-motion: reduce) {
          .ds2-top-nav__panel,
          .ds2-top-nav__panel--open {
            transition: none;
          }
        }
      }
    
      // Open menu, mobile and tablet.
      &--menu-open {
        .ds2-top-nav__nav {
          display: block;
        }
      }
    
      // Search open. --search-closing keeps the open layout while it animates closed.
      &--search-open,
      &--search-closing {
        // The arrow sits against the box, and the pair is centred between the edge and the burger.
        .ds2-top-nav__search-close {
          display: inline-flex;
          margin-left: auto;
        }
    
        .ds2-top-nav__search {
          display: flex;
          max-width: $topNavSearchSlideEndWidth;
          margin-inline: $spacing-3 auto;
          overflow: hidden;
        }
      }
    
      // Once search is open, the logo and search icon are hidden and leave the tab order.
      &--search-open {
        .ds2-top-nav__logo,
        .ds2-top-nav__search-toggle {
          display: none;
        }
    
        .ds2-top-nav__search {
          animation: ds2-top-nav-search-grow $topNavSearchSlideDuration ease;
    
          @media (prefers-reduced-motion: reduce) {
            animation: none;
          }
        }
      }
    
      // While the box opens or closes, the logo and search icon fade where they stand.
      // Must come after &--search-open.
      &--search-opening,
      &--search-closing {
        .ds2-top-nav__logo,
        .ds2-top-nav__search-toggle {
          display: inline-flex;
          // Taken out of the flow so the box has room straight away.
          position: absolute;
          top: $spacing-5;
          bottom: $spacing-5;
          margin-block: auto;
          pointer-events: none;
        }
    
        .ds2-top-nav__logo {
          left: $spacing-5;
        }
    
        // Just left of the burger.
        .ds2-top-nav__search-toggle {
          right: unquote("calc(#{$spacing-5} + #{$topNavMenuButtonSize})");
        }
      }
    
      &--search-opening {
        .ds2-top-nav__logo,
        .ds2-top-nav__search-toggle {
          animation: ds2-top-nav-fade-out $topNavSearchSlideDuration ease forwards;
        }
    
        .ds2-top-nav__search-close {
          animation: ds2-top-nav-fade-in $topNavSearchSlideDuration ease;
        }
      }
    
      // The same in reverse. Nothing in the closing box takes clicks.
      &--search-closing {
        .ds2-top-nav__logo,
        .ds2-top-nav__search-toggle {
          animation: ds2-top-nav-fade-in $topNavSearchSlideDuration ease;
        }
    
        .ds2-top-nav__search-close {
          pointer-events: none;
          animation: ds2-top-nav-fade-out $topNavSearchSlideDuration ease forwards;
        }
    
        .ds2-top-nav__search {
          pointer-events: none;
          animation: ds2-top-nav-search-shrink $topNavSearchSlideDuration ease forwards;
        }
      }
    
      // From tablet up the logo stays while search is open, and the box is centred between it and the
      // burger. Must come after the rules above. Desktop overrides it.
      @media (min-width: $topNavTabletBreakpoint) {
        &--search-open,
        &--search-closing {
          // Gives up its auto margin so the arrow and box are centred in the space that's left.
          .ds2-top-nav__logo {
            margin-right: 0;
          }
    
          .ds2-top-nav__search {
            max-width: $topNavTabletSearchSlideEndWidth;
          }
        }
    
        &--search-open {
          .ds2-top-nav__logo {
            display: inline-flex;
          }
    
          .ds2-top-nav__search {
            animation: ds2-top-nav-tablet-search-grow $topNavSearchSlideDuration ease;
    
            @media (prefers-reduced-motion: reduce) {
              animation: none;
            }
          }
        }
    
        // The logo stays where it is rather than fading.
        &--search-opening,
        &--search-closing {
          .ds2-top-nav__logo {
            position: static;
            pointer-events: auto;
            animation: none;
          }
        }
    
        &--search-closing .ds2-top-nav__search {
          animation: ds2-top-nav-tablet-search-shrink $topNavSearchSlideDuration ease forwards;
        }
      }
    
      // Desktop layout, from _ds2-top-nav-desktop.scss.
      @include ds2-top-nav-desktop;
    
      // Search animations. Opening and closing need different names, or the browser won't replay them.
      @keyframes ds2-top-nav-fade-out {
        from {
          opacity: 1;
        }
    
        to {
          opacity: 0;
        }
      }
    
      @keyframes ds2-top-nav-fade-in {
        from {
          opacity: 0;
        }
    
        to {
          opacity: 1;
        }
      }
    
      // Fades in over the last 15%.
      @keyframes ds2-top-nav-icon-fade-in-late {
        from {
          opacity: 0;
        }
    
        85% {
          opacity: 0;
        }
    
        to {
          opacity: 1;
        }
      }
    
      @keyframes ds2-top-nav-search-shrink {
        from {
          max-width: $topNavSearchSlideEndWidth;
          opacity: 1;
        }
    
        67% {
          opacity: 1;
        }
    
        to {
          max-width: $topNavIconButtonSize;
          opacity: 0;
        }
      }
    
      @keyframes ds2-top-nav-search-grow {
        from {
          max-width: $topNavIconButtonSize;
          opacity: 0;
        }
    
        33% {
          opacity: 1;
        }
    
        to {
          max-width: $topNavSearchSlideEndWidth;
          opacity: 1;
        }
      }
    
      // The same as the mobile ones, with room left for the logo.
      @keyframes ds2-top-nav-tablet-search-grow {
        from {
          max-width: $topNavIconButtonSize;
          opacity: 0;
        }
    
        33% {
          opacity: 1;
        }
    
        to {
          max-width: $topNavTabletSearchSlideEndWidth;
          opacity: 1;
        }
      }
    
      @keyframes ds2-top-nav-tablet-search-shrink {
        from {
          max-width: $topNavTabletSearchSlideEndWidth;
          opacity: 1;
        }
    
        67% {
          opacity: 1;
        }
    
        to {
          max-width: $topNavIconButtonSize;
          opacity: 0;
        }
      }
    
      @keyframes ds2-top-nav-desktop-search-grow {
        from {
          max-width: $topNavIconButtonSize;
        }
    
        to {
          max-width: $topNavDesktopSearchMaxWidth;
        }
      }
    
      // Fades out over the same 15% the icon fades in.
      @keyframes ds2-top-nav-desktop-search-shrink {
        from {
          max-width: $topNavDesktopSearchMaxWidth;
          opacity: 1;
        }
    
        85% {
          opacity: 1;
        }
    
        to {
          max-width: $topNavIconButtonSize;
          opacity: 0;
        }
      }
    
      // Text for screen readers only. In px, as it's a fixed 1px box.
      &__visually-hidden {
        position: absolute !important;
        width: 1px;
        height: 1px;
        margin: -1px;
        padding: 0;
        overflow: hidden;
        clip-path: inset(50%);
        white-space: nowrap;
        border: 0;
      }
    }
  • URL: /components/raw/ds2-top-nav/_top-nav.scss
  • Filesystem Path: src/library/ds2/02-components/top-nav/_top-nav.scss
  • Size: 15.4 KB
  • Content:
    // Must match $topNavDesktopBreakpoint in the SCSS.
    const desktopMediaQuery = '(min-width: 80rem)';
    
    const openMenuClass = 'ds2-top-nav--menu-open';
    const openSearchClass = 'ds2-top-nav--search-open';
    const openPanelClass = 'ds2-top-nav__panel--open';
    // Held while the search box animates open or closed.
    const closingSearchClass = 'ds2-top-nav--search-closing';
    const openingSearchClass = 'ds2-top-nav--search-opening';
    
    // Must match $topNavSearchSlideDuration in the SCSS.
    const searchSlideDuration = 400;
    
    export const ds2Header = () => {
      const desktopQuery = window.matchMedia(desktopMediaQuery);
    
      document.querySelectorAll('.ds2-top-nav').forEach((header) => {
        // Don't set up the same header twice.
        if (header.classList.contains('ds2-top-nav--js')) return;
    
        initTopNav(header, desktopQuery);
      });
    };
    
    const initTopNav = (header, desktopQuery) => {
      const nav = header.querySelector('.ds2-top-nav__nav');
      if (!nav) return;
    
      const menuToggle = header.querySelector('.ds2-top-nav__menu-toggle');
      const searchToggle = header.querySelector('.ds2-top-nav__search-toggle');
      const searchClose = header.querySelector('.ds2-top-nav__search-close');
    
      // Where the nav sits in the markup, so it can be put back below the breakpoint.
      const bar = header.querySelector('.ds2-top-nav__bar');
      const navHome = nav.nextElementSibling;
      const searchGroup = header.querySelector('.ds2-top-nav__search-group');
      const searchForm = header.querySelector('.ds2-top-nav__search');
      const searchInput = header.querySelector('.ds2-top-nav__search-input');
    
      // Each button controls the panel straight after it. The ids and ARIA are added here,
      // so the markup has nothing to number.
      const panelFor = new Map();
      const buttonFor = new Map();
    
      const idBase = nav.id || 'ds2-top-nav-menu';
      let panelNumber = 0;
    
      Array.from(nav.querySelectorAll('.ds2-top-nav__item > button')).forEach((button) => {
        const panel = button.nextElementSibling;
    
        if (panel && panel.classList.contains('ds2-top-nav__panel')) {
          panelFor.set(button, panel);
          buttonFor.set(panel, button);
    
          panelNumber += 1;
          if (!panel.id) panel.id = `${idBase}-panel-${panelNumber}`;
          button.setAttribute('aria-controls', panel.id);
    
          button.setAttribute('aria-expanded', 'false');
        }
      });
    
      const reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)');
      let searchClosingTimer;
    
      const isDesktop = () => desktopQuery.matches;
      const isMenuOpen = () => header.classList.contains(openMenuClass);
      const isSearchOpen = () => header.classList.contains(openSearchClass);
      const isExpanded = (button) => button.getAttribute('aria-expanded') === 'true';
      const isTopLevel = (button) => !button.closest('.ds2-top-nav__panel');
      const openButtons = () => Array.from(panelFor.keys()).filter(isExpanded);
    
      // An item's own button, not one in a nested panel.
      const itemButton = (item) => {
        const button = item.querySelector(':scope > button');
        return button && panelFor.has(button) ? button : null;
      };
    
      // On mobile a panel covers the list it came from, so everything behind it is made inert
      // and can't be tabbed to.
      const setCoveredInert = (button, covered) => {
        const item = button.closest('.ds2-top-nav__item');
        if (!item || !item.parentElement) return;
    
        Array.from(item.parentElement.children).forEach((sibling) => {
          if (sibling !== item) sibling.inert = covered;
        });
        button.inert = covered;
    
        const parentPanel = item.closest('.ds2-top-nav__panel');
        const parentBack = parentPanel && parentPanel.querySelector(':scope > .ds2-top-nav__back');
        if (parentBack) parentBack.inert = covered;
      };
    
      const closePanel = (button) => {
        const panel = panelFor.get(button);
    
        // Close anything open inside it first.
        Array.from(panel.querySelectorAll('button[aria-expanded="true"]')).forEach((nestedButton) => {
          if (panelFor.has(nestedButton)) closePanel(nestedButton);
        });
    
        button.setAttribute('aria-expanded', 'false');
        panel.classList.remove(openPanelClass);
        setCoveredInert(button, false);
      };
    
      const closeAllPanels = () => {
        openButtons().forEach(closePanel);
      };
    
      const openPanel = (button) => {
        const panel = panelFor.get(button);
        const item = button.closest('.ds2-top-nav__item');
    
        // Only one panel open per list.
        if (item && item.parentElement) {
          Array.from(item.parentElement.children).forEach((sibling) => {
            const siblingButton = itemButton(sibling);
    
            if (sibling !== item && siblingButton && isExpanded(siblingButton)) {
              closePanel(siblingButton);
            }
          });
        }
    
        button.setAttribute('aria-expanded', 'true');
        panel.classList.add(openPanelClass);
    
        // On mobile the panel covers the button, so focus moves into the panel.
        if (!isDesktop()) {
          setCoveredInert(button, true);
    
          // Or the back button, if the panel has no links.
          const firstItem = panel.querySelector(':scope > .ds2-top-nav__list a[href], :scope > .ds2-top-nav__list button')
            || panel.querySelector(':scope > .ds2-top-nav__back');
    
          if (firstItem) firstItem.focus();
        }
      };
    
      const syncSearchToggle = () => {
        if (searchToggle) searchToggle.setAttribute('aria-expanded', String(isSearchOpen()));
      };
    
      // On desktop the search box replaces the nav links, so any open dropdown closes.
      const setSearchOpen = (open) => {
        if (open && isDesktop()) closeAllPanels();
    
        const wasOpen = isSearchOpen();
    
        header.classList.toggle(openSearchClass, open);
        syncSearchToggle();
    
        // Hold the opening or closing class for the length of the animation, unless reduced motion is on.
        clearTimeout(searchClosingTimer);
    
        header.classList.remove(openingSearchClass);
    
        if (!open && wasOpen && !reducedMotion.matches) {
          header.classList.add(closingSearchClass);
          // Nothing in it can take focus while it closes.
          if (searchForm) searchForm.inert = true;
    
          searchClosingTimer = setTimeout(() => {
            header.classList.remove(closingSearchClass);
            if (searchForm) searchForm.inert = false;
          }, searchSlideDuration);
        } else if (open && !wasOpen && !reducedMotion.matches) {
          header.classList.remove(closingSearchClass);
          if (searchForm) searchForm.inert = false;
    
          header.classList.add(openingSearchClass);
          searchClosingTimer = setTimeout(() => {
            header.classList.remove(openingSearchClass);
          }, searchSlideDuration);
        } else {
          header.classList.remove(closingSearchClass);
          if (searchForm) searchForm.inert = false;
        }
      };
    
      // The menu and the search box are independent, so this leaves search alone.
      const setMenuOpen = (open) => {
        header.classList.toggle(openMenuClass, open);
        if (menuToggle) menuToggle.setAttribute('aria-expanded', String(open));
    
        if (!open) closeAllPanels();
      };
    
      // Clicking or tabbing away only closes search if nothing has been typed.
      const closeSearchIfEmpty = () => {
        if (isSearchOpen() && !(searchInput && searchInput.value)) setSearchOpen(false);
      };
    
      // For clicking outside and focus leaving the header.
      const closeAll = () => {
        closeAllPanels();
        setMenuOpen(false);
        closeSearchIfEmpty();
      };
    
      panelFor.forEach((panel, button) => {
        // Click only: hovering opens nothing.
        button.addEventListener('click', () => {
          if (isExpanded(button)) {
            closePanel(button);
          } else {
            openPanel(button);
          }
        });
      });
    
      // Back closes the panel and returns focus to the button that opened it.
      Array.from(nav.querySelectorAll('.ds2-top-nav__back')).forEach((backButton) => {
        const panel = backButton.closest('.ds2-top-nav__panel');
        const button = panel && buttonFor.get(panel);
        if (!button) return;
    
        backButton.addEventListener('click', () => {
          closePanel(button);
          button.focus();
        });
      });
    
      if (menuToggle) {
        menuToggle.addEventListener('click', () => {
          setMenuOpen(!isMenuOpen());
        });
      }
    
      if (searchToggle) {
        searchToggle.addEventListener('click', () => {
          setSearchOpen(true);
    
          if (searchInput) searchInput.focus();
        });
      }
    
      // Closing returns focus to the search icon.
      if (searchClose) {
        searchClose.addEventListener('click', () => {
          setSearchOpen(false);
          if (searchToggle) searchToggle.focus();
        });
      }
    
      // Escape closes the top-most open thing and returns focus to whatever opened it.
      // It's on the document, so it works without focus being in the header.
      document.addEventListener('keydown', (event) => {
        if (event.key !== 'Escape') return;
    
        // Let Escape clear a search input with text in it first.
        if (event.target === searchInput && searchInput.value) return;
    
        const focusInHeader = header.contains(document.activeElement);
    
        // Search closes first.
        if (isSearchOpen()) {
          setSearchOpen(false);
          if (focusInHeader && searchToggle) searchToggle.focus();
          return;
        }
    
        if (isDesktop()) {
          // Desktop: close the dropdown and focus its top-level button.
          const topButton = openButtons().find(isTopLevel);
          if (!topButton) return;
    
          closeAllPanels();
          if (focusInHeader) topButton.focus();
        } else if (isMenuOpen()) {
          // Mobile: close the whole menu and focus the burger.
          closeAll();
          if (focusInHeader && menuToggle) menuToggle.focus();
        }
      });
    
      document.addEventListener('click', (event) => {
        if (!header.contains(event.target)) closeAll();
      });
    
      header.addEventListener('focusin', (event) => {
        if (!isDesktop()) return;
    
        // Desktop: tabbing elsewhere in the header closes dropdowns focus has left.
        openButtons().forEach((button) => {
          const item = button.closest('.ds2-top-nav__item');
    
          if (isExpanded(button) && item && !item.contains(event.target)) closePanel(button);
        });
    
        // Not when moving to the close arrow, or the box would vanish mid-click.
        const movingToClose = searchClose && searchClose.contains(event.target);
    
        if (searchForm && !searchForm.contains(event.target) && !movingToClose) closeSearchIfEmpty();
      });
    
      header.addEventListener('focusout', (event) => {
        // relatedTarget is null when the window loses focus, so leave things open then.
        if (event.relatedTarget && !header.contains(event.relatedTarget)) closeAll();
      });
    
      // Keeps the tab order matching the layout: on desktop the nav moves between the logo and search,
      // and below the breakpoint it goes back where it was.
      const placeNav = () => {
        if (!bar || !searchGroup) return;
    
        if (isDesktop()) {
          if (nav.parentNode !== bar || nav.nextElementSibling !== searchGroup) {
            bar.insertBefore(nav, searchGroup);
          }
        } else if (nav.parentNode !== header || nav.nextElementSibling !== navHome) {
          header.insertBefore(nav, navHome);
        }
      };
    
      // Crossing the breakpoint closes everything before the nav moves.
      desktopQuery.addEventListener('change', () => {
        closeAll();
        setSearchOpen(false);
        placeNav();
      });
    
      // matchMedia only fires on a change, so place it for the starting width too.
      placeNav();
    
      setMenuOpen(false);
      header.classList.add('ds2-top-nav--js');
    };
    
  • URL: /components/raw/ds2-top-nav/top-nav.module.js
  • Filesystem Path: src/library/ds2/02-components/top-nav/top-nav.module.js
  • Size: 11.3 KB
{
  "navLabel": "Main",
  "menuLabel": "Menu",
  "logo": {
    "url": "https://www.leeds.ac.uk/",
    "label": "University of Leeds homepage"
  },
  "search": {
    "action": "https://www.leeds.ac.uk/search",
    "inputName": "q",
    "label": "Search leeds.ac.uk",
    "placeholder": "Search",
    "submitLabel": "Search",
    "openLabel": "Search",
    "closeLabel": "Close search",
    "hiddenFields": [
      {
        "name": "searchOption",
        "value": "searchSite"
      }
    ]
  },
  "level1Options": [
    {
      "title": "Study",
      "level2Options": [
        {
          "title": "Course search",
          "url": "#"
        },
        {
          "title": "Undergraduate",
          "level3Options": [
            {
              "title": "Undergraduate home page",
              "url": "#"
            },
            {
              "title": "Clearing 2026",
              "url": "#"
            },
            {
              "title": "Undergraduate course search",
              "url": "#"
            },
            {
              "title": "Fees and funding",
              "url": "#"
            },
            {
              "title": "Student life",
              "url": "#"
            }
          ]
        },
        {
          "title": "Online courses",
          "level3Options": [
            {
              "title": "Overview",
              "url": "#"
            },
            {
              "title": "Online Masters study",
              "url": "#"
            },
            {
              "title": "Short online courses",
              "url": "#"
            },
            {
              "title": "PhD by distance learning",
              "url": "#"
            }
          ]
        },
        {
          "title": "Masters",
          "level3Options": [
            {
              "title": "Masters home page",
              "url": "#"
            },
            {
              "title": "Postgraduate open day",
              "url": "#"
            },
            {
              "title": "Masters course search",
              "url": "#"
            },
            {
              "title": "Applying",
              "url": "#"
            },
            {
              "title": "Fees and funding",
              "url": "#"
            }
          ]
        },
        {
          "title": "Research degrees",
          "url": "#"
        },
        {
          "title": "International students",
          "url": "#"
        },
        {
          "title": "Foundation courses",
          "url": "#"
        },
        {
          "title": "Mature students",
          "url": "#"
        }
      ]
    },
    {
      "title": "Faculties",
      "level2Options": [
        {
          "title": "Arts, Humanities and Cultures",
          "url": "#"
        },
        {
          "title": "Biological Sciences",
          "url": "#"
        },
        {
          "title": "Business School",
          "url": "#"
        },
        {
          "title": "Engineering and Physical Sciences",
          "url": "#"
        },
        {
          "title": "Environment",
          "url": "#"
        },
        {
          "title": "Medicine and Health",
          "url": "#"
        },
        {
          "title": "Social Sciences",
          "url": "#"
        },
        {
          "title": "Faculty contacts",
          "url": "#"
        }
      ]
    },
    {
      "title": "Research",
      "level2Options": [
        {
          "title": "Research overview",
          "url": "#"
        },
        {
          "title": "Research excellence and impact",
          "url": "#"
        },
        {
          "title": "Strategy and environment",
          "url": "#"
        },
        {
          "title": "Researcher and staff development",
          "url": "#"
        },
        {
          "title": "Public engagement",
          "url": "#"
        },
        {
          "title": "Research and innovation contacts",
          "url": "#"
        }
      ]
    },
    {
      "title": "Business",
      "level2Options": [
        {
          "title": "Overview",
          "url": "#"
        },
        {
          "title": "Access our expertise",
          "url": "#"
        },
        {
          "title": "Training and development",
          "url": "#"
        },
        {
          "title": "Recruit our students and graduates",
          "url": "#"
        },
        {
          "title": "Use our facilities",
          "url": "#"
        },
        {
          "title": "Support for small businesses",
          "url": "#"
        },
        {
          "title": "Our business partners",
          "url": "#"
        }
      ]
    },
    {
      "title": "About",
      "level2Options": [
        {
          "title": "Campus map",
          "url": "#"
        },
        {
          "title": "Our history",
          "url": "#"
        },
        {
          "title": "Faculties",
          "url": "#"
        },
        {
          "title": "Governance",
          "url": "#"
        },
        {
          "title": "Reports, accounts and policies",
          "url": "#"
        },
        {
          "title": "Strategies and values",
          "url": "#"
        },
        {
          "title": "Equality",
          "url": "#"
        },
        {
          "title": "Executive team",
          "url": "#"
        },
        {
          "title": "News",
          "url": "#"
        },
        {
          "title": "Events",
          "url": "#"
        }
      ]
    },
    {
      "title": "Alumni",
      "url": "#"
    }
  ]
}