Global quicklinks provide a quick and consistent way for users to access popular or important content from across University of Leeds websites.
The “quicklinks” contents should match that of the main University of Leeds website. You should not alter or add additional links to those found on the main University of Leeds website. This helps to provide consistency of user experience as site visitors move between and return to any University of Leeds site that uses the Design System.
The @uol-header-global-quicklinks component should be included as part of the @uol-header-global-masthead
{% if quicklinks.length %}
<nav class="uol-quicklinks" aria-label="University links">
<ul class="uol-quicklinks__list">
{% for links_group in quicklinks %}
<li class="uol-quicklinks__group">
{{ links_group.title }}
{% if links_group.links.length %}
<ul class="uol-quicklinks__group__list">
{% for link in links_group.links %}
<li class="uol-quicklinks__group__item">
<a class="uol-quicklinks__group__link" href="{{ link.url }}">{{ link.title }}</a>
</li>
{% endfor %}
</ul>
{% endif %}
</li>
{% endfor %}
</ul>
</nav>
{% endif %}
<nav class="uol-quicklinks" aria-label="University links">
<ul class="uol-quicklinks__list">
<li class="uol-quicklinks__group">
Students
<ul class="uol-quicklinks__group__list">
<li class="uol-quicklinks__group__item">
<a class="uol-quicklinks__group__link" href="/mobile-app">Mobile App</a>
</li>
<li class="uol-quicklinks__group__item">
<a class="uol-quicklinks__group__link" href="/minerva">Minerva</a>
</li>
<li class="uol-quicklinks__group__item">
<a class="uol-quicklinks__group__link" href="/for-students">For Students</a>
</li>
<li class="uol-quicklinks__group__item">
<a class="uol-quicklinks__group__link" href="https://library.leeds.ac.uk">Library</a>
</li>
<li class="uol-quicklinks__group__item">
<a class="uol-quicklinks__group__link" href="/it">IT</a>
</li>
<li class="uol-quicklinks__group__item">
<a class="uol-quicklinks__group__link" href="/campus-map">Campus map</a>
</li>
</ul>
</li>
<li class="uol-quicklinks__group">
Staff
<ul class="uol-quicklinks__group__list">
<li class="uol-quicklinks__group__item">
<a class="uol-quicklinks__group__link" href="/staff/for-staff">For Staff</a>
</li>
<li class="uol-quicklinks__group__item">
<a class="uol-quicklinks__group__link" href="/staff/services-a-z">Services A-Z</a>
</li>
<li class="uol-quicklinks__group__item">
<a class="uol-quicklinks__group__link" href="/staff/staff-a-z">Staff A-Z</a>
</li>
<li class="uol-quicklinks__group__item">
<a class="uol-quicklinks__group__link" href="/staff/faculty-a-z">Faculties A-Z</a>
</li>
<li class="uol-quicklinks__group__item">
<a class="uol-quicklinks__group__link" href="/staff/student-education-service">Student Education Service</a>
</li>
<li class="uol-quicklinks__group__item">
<a class="uol-quicklinks__group__link" href="/staff/hr">HR</a>
</li>
<li class="uol-quicklinks__group__item">
<a class="uol-quicklinks__group__link" href="/staff/it">IT</a>
</li>
<li class="uol-quicklinks__group__item">
<a class="uol-quicklinks__group__link" href="/campus-map">Campus map</a>
</li>
</ul>
</li>
<li class="uol-quicklinks__group">
Faculties
<ul class="uol-quicklinks__group__list">
<li class="uol-quicklinks__group__item">
<a class="uol-quicklinks__group__link" href="/faculty/arts-humanities-cultures">Faculty of Arts, Humanities and Cultures</a>
</li>
<li class="uol-quicklinks__group__item">
<a class="uol-quicklinks__group__link" href="/faculty/biological-sciences">Faculty of Biological Sciences</a>
</li>
<li class="uol-quicklinks__group__item">
<a class="uol-quicklinks__group__link" href="/faculty/business">Faculty of Business</a>
</li>
<li class="uol-quicklinks__group__item">
<a class="uol-quicklinks__group__link" href="/faculty/engineering-physical-sciences">Faculty of Engineering and Physical Sciences</a>
</li>
<li class="uol-quicklinks__group__item">
<a class="uol-quicklinks__group__link" href="/faculty/environment">Faculty of Environment</a>
</li>
<li class="uol-quicklinks__group__item">
<a class="uol-quicklinks__group__link" href="/faculty/medicine-health">Faculty of Medicine and Health</a>
</li>
<li class="uol-quicklinks__group__item">
<a class="uol-quicklinks__group__link" href="/faculty/social-sciences">Faculty of Social Sciences</a>
</li>
</ul>
</li>
</ul>
</nav>
.uol-quicklinks {
.no-js & {
display: none;
}
position: relative;
flex-grow: 2;
@include media("<uol-media-m") {
width: calc(100% - 136px);
background: $color-black--dark;
.uol-global-masthead--quicklinks-expanded & {
width: auto;
}
}
@include media(">=uol-media-m") {
margin-right: $spacing-5;
}
@include media(">=uol-media-l") {
max-width: calc(100% - 400px - 20rem);
}
}
.uol-quicklinks__list {
list-style: none;
margin: 0;
padding: 0;
display: flex;
justify-content: flex-end;
text-align: left;
}
.uol-quicklinks__button {
position: relative;
margin: 0;
white-space: nowrap;
background: transparent;
color: $color-white--dark;
font-size: 1rem;
font-weight: 500;
border: none;
padding: 0.9em 0.5em;
&:focus {
outline: 2px dotted transparent;
outline-offset: -6px;
}
@include media("<uol-media-m") {
&[aria-expanded="true"] {
@include font-size-responsive(1.75rem, 2.25rem, 3rem);
@include line-height-responsive(2.25rem, 3rem, 3.5rem);
display: block;
text-align: left;
width: 100vw;
border-bottom: 2px solid $color-brand--bright;
margin-top: -#{$spacing-2};
padding: 1.312rem $spacing-7 1.312rem $spacing-4;
}
}
&::after {
content: "";
position: absolute;
bottom: 0.2em;
left: 0.2em;
right: calc(100% - 0.2em);
height: 0.375rem;
background-color: rgba($color-brand--bright, 0);
transition: right 0.4s ease 0.1s;
@include media(">=uol-media-m") {
height: $spacing-2;
}
@media (-ms-high-contrast: active),
(forced-colors: active) {
background-color: WindowText;
background-color: CanvasText;
}
}
&:hover,
&:focus {
&::after {
right: 0.2em;
background-color: $color-brand--bright;
}
}
&[aria-expanded="true"] {
&::before {
content: "";
position: absolute;
right: 0.3rem;
bottom: 0.6rem;
width: 0;
height: 0;
border: $spacing-2 solid transparent;
border-top: none;
border-bottom-color: $color-white;
filter: drop-shadow(0 -0.05rem 0.05rem rgba($color-black, 0.1));
z-index: 2;
@include media("<=uol-media-m") {
display: none;
}
}
}
&[aria-expanded="true"] {
@include media("<=uol-media-m") {
&::after {
display: none;
}
&:focus {
text-decoration: underline;
}
&:focus:not(:focus-visible) {
text-decoration: none;
}
&:focus-visible {
outline-color: transparent;
text-decoration: underline;
text-underline-offset: 4px;
text-decoration-thickness: $spacing-2;
text-decoration-color: $color-brand--bright;
}
}
}
}
.uol-quicklinks__button__icon {
position: relative;
display: inline-block;
width: 1em;
height: 1em;
margin-left: 0.2em;
margin-right: -0.2em;
&::before,
&::after {
content: "";
display: block;
position: absolute;
width: 0.5em;
height: 0.13em;
background: $color-white--dark;
bottom: 20%;
@media (-ms-high-contrast: active) {
background: ButtonText;
}
}
&::before {
left: 50%;
transform: translateX(-76%) rotate(45deg);
}
&::after {
right: 50%;
transform: translateX(76%) rotate(-45deg);
}
.uol-quicklinks__group--expanded & {
@include media("<uol-media-m") {
position: absolute;
padding: $spacing-4;
right: $spacing-3;
top: 33%;
transform: translateY(-50%);
}
@include media(">=uol-media-m") {
}
&::before {
transform: translateX(-76%) rotate(315deg);
}
&::after {
transform: translateX(76%) rotate(45deg);
}
}
}
.uol-quicklinks__group {
position: relative;
@include media(">=uol-media-m") {
padding-right: $spacing-5;
}
.uol-quicklinks__group--more & {
font-weight: $font-weight-bold--sans-serif;
padding-left: $spacing-4;
@include media(">=uol-media-m") {
width: 50%;
}
}
}
.uol-quicklinks__group--sibling-expanded {
@include media("<uol-media-m") {
position: absolute !important;
clip: rect(1px, 1px, 1px, 1px);
height: 1px;
width: 1px;
overflow: hidden;
left: -9999px;
}
}
.uol-quicklinks__group__list,
.uol-quicklinks__more-list {
font-size: 1.75rem;
display: none;
padding: $spacing-4;
list-style: none;
background: $color-black;
color: $color-white;
@include media(">=uol-media-m") {
position: absolute;
top: calc(100% - #{$spacing-3});
right: 0;
box-sizing: border-box;
width: 375px;
max-width: 100vw;
background: $color-white;
font-weight: $font-weight-bold--sans-serif;
color: $color-font;
border: 1px solid rgba($color-border--light, 0.2);
border-radius: $spacing-3;
padding-top: $spacing-4;
z-index: 1;
transform: translateX($spacing-7);
box-shadow:
0 15px 24px 0 rgba(10, 2, 2, 0.15),
0 5px 10px 0 rgba(33, 33, 33, 0.5);
}
@include media(">=uol-media-l") {
right: auto;
left: 0;
transform: translateX(-#{$spacing-7});
&::before {
right: auto;
left: 3.25rem;
}
}
.uol-quicklinks__button[aria-expanded="true"] + & {
display: block;
}
}
.uol-quicklinks__group__list {
.uol-quicklinks__group--more & {
margin-left: -#{$spacing-4};
}
.uol-quicklinks__more-list & {
display: block;
@include media(">=uol-media-m") {
position: relative;
width: auto;
top: auto;
left: auto;
right: 0;
padding-top: 0;
padding-bottom: 0;
transform: none;
box-shadow: none;
border: 0;
}
}
}
.uol-quicklinks__more-list {
@include media(">=uol-media-l") {
width: 766px;
transform: translateX(-40%);
padding-top: $spacing-6;
.uol-quicklinks__button[aria-expanded="true"] + & {
display: flex;
}
}
}
.uol-quicklinks__group__item {
padding: 0;
.uol-quicklinks__group--more & {
padding-left: 0;
margin-left: -#{$spacing-4};
margin-right: -#{$spacing-4};
}
}
.uol-quicklinks__group__link {
position: relative;
display: block;
padding: $spacing-4;
font-size: 1.125rem;
text-decoration: none;
border-radius: 6px;
transition: background 0.3s ease;
@include media(">=uol-media-m") {
color: $color-font--light;
}
svg {
position: relative;
margin-top: -0.35em;
top: 0.35em;
left: 0;
margin-left: 0.5em;
fill: rgba($color-brand--bright, 0);
opacity: 0;
transition: all 0.3s ease 0.2s;
@media (-ms-high-contrast: active) {
fill: windowText;
}
}
&:hover,
&:focus {
background: $color-black--dark;
text-decoration: underline;
@include media(">=uol-media-m") {
background: $colour-warmgrey—faded;
color: $color-font--dark;
}
svg {
left: 0.4em;
opacity: 1;
fill: $color-brand--bright;
@media (-ms-high-contrast: active) {
fill: windowText;
}
}
}
}
const svgRightArrow = `<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24" focusable="false" aria-hidden="true">
<path d="M0 0h24v24H0z" fill="none"/>
<path d="M12 4l-1.41 1.41L16.17 11H4v2h12.17l-5.58 5.59L12 20l8-8z"/>
</svg>`
export const globalQuicklinks = () => {
// Utility getSiblings()
const getSiblings = (element) => {
return Array.prototype.filter.call(element.parentNode.children, function (sibling) {
return sibling !== element;
});
};
// Utility isDescendant()
const isDescendant = (el, parent) => {
let isChild = false
if (el === parent) { //is this the element itself?
isChild = true
}
while (el = el.parentNode) {
if (el == parent) {
isChild = true
}
}
return isChild
}
const groupButtons = (quicklinksContainer) => {
// Add buttons to link group parents
// const groupTitles = quicklinksContainer.querySelectorAll('.uol-quicklinks__group--title-container')
const groups = quicklinksContainer.querySelectorAll('.uol-quicklinks__group')
groups.forEach( (group) => {
const title = group.firstChild.textContent.trim()
group.firstChild.remove()
const button = document.createElement('button')
button.classList.add('uol-quicklinks__button')
button.innerHTML = `${title}<span class="uol-quicklinks__button__icon"></span>`
button.setAttribute('aria-expanded', 'false')
group.prepend(button)
})
}
const moreWrap = (quicklinksContainer) => {
// If buttons are too wide for container wrap overflowing groups in "More"
const quicklinksGroups = quicklinksContainer.querySelectorAll('.uol-quicklinks__group')
const containerWidth = quicklinksContainer.clientWidth
let groupsWidth = 0
let moreStart = null
let moreItems = document.createElement('li')
moreItems.classList.add('uol-quicklinks__group')
moreItems.classList.add('uol-quicklinks__group--more')
moreItems.innerHTML = `
<button class="uol-quicklinks__button uol-quicklinks__button--more" aria-expanded="false">More<span class="uol-quicklinks__button__icon"></span></button>
`
let moreItemsList = document.createElement('ul')
moreItemsList.classList.add('uol-quicklinks__more-list')
quicklinksGroups.forEach( (quicklinksGroup, i) => {
const groupWidth = quicklinksGroup.offsetWidth
groupsWidth += groupWidth
if (groupsWidth > containerWidth) {
if (!moreStart) {
moreStart = true
let prevGroup = quicklinksGroups[i - 1]
let prevGroupClone = prevGroup.cloneNode(true)
// Remove button
const newHeadingPrev = document.createTextNode(prevGroupClone.querySelector('button').innerText)
prevGroupClone.querySelector('button').remove()
prevGroupClone.prepend(newHeadingPrev)
moreItemsList.appendChild(prevGroupClone)
prevGroup.remove()
}
const itemClone = quicklinksGroup.cloneNode(true)
// Remove button
const newHeading = document.createTextNode(itemClone.querySelector('button').innerText)
itemClone.querySelector('button').remove()
itemClone.prepend(newHeading)
moreItemsList.appendChild(itemClone)
quicklinksGroup.remove()
}
})
if (moreStart) {
moreItems.appendChild(moreItemsList)
quicklinksContainer.querySelector('.uol-quicklinks__list').append(moreItems)
}
}
const buttonAction = (quicklinksContainer) => {
const buttons = quicklinksContainer.querySelectorAll('.uol-quicklinks__button')
buttons.forEach( (button) => {
button.onclick = () => {
let expanded = button.getAttribute('aria-expanded') === 'true' || false
const siblings = getSiblings(button.parentElement)
const containerParent = quicklinksContainer.parentNode
siblings.forEach( (sibling) => {
sibling.classList.remove('uol-quicklinks__group--expanded')
})
button.setAttribute('aria-expanded', !expanded)
if (!expanded) {
button.parentNode.classList.add('uol-quicklinks__group--expanded')
// Alter siblings
siblings.forEach( (sibling) => {
sibling.classList.add('uol-quicklinks__group--sibling-expanded')
sibling.querySelector('button').setAttribute('aria-expanded', 'false')
})
// Alter containerParent parent to hide container's siblings
if (containerParent.classList.contains('uol-global-masthead')) {
containerParent.classList.add('uol-global-masthead--quicklinks-expanded')
}
} else {
button.parentNode.classList.remove('uol-quicklinks__group--expanded')
// Alter siblings
siblings.forEach( (sibling) => {
sibling.classList.remove('uol-quicklinks__group--sibling-expanded')
})
// Alter containerParent parent to show container's siblings
if (containerParent.classList.contains('uol-global-masthead')) {
containerParent.classList.remove('uol-global-masthead--quicklinks-expanded')
}
}
}
})
}
const focusListener = (quicklinksContainer) => {
const subLists = quicklinksContainer.querySelectorAll('.uol-quicklinks__group')
const moreList = quicklinksContainer.querySelector('.uol-quicklinks__more-list')
const buttons = quicklinksContainer.querySelectorAll('.uol-quicklinks__button')
subLists.forEach((subList) => {
subList.addEventListener('focusout', (event) => {
if (event.relatedTarget) {
if ( !isDescendant(event.relatedTarget, subList) && !isDescendant(event.relatedTarget, moreList) ) {
quicklinksContainer.parentNode.classList.remove('uol-global-masthead--quicklinks-expanded')
buttons.forEach( (button) => {
button.parentNode.classList.remove('uol-quicklinks__group--expanded')
button.parentNode.classList.remove('uol-quicklinks__group--sibling-expanded')
button.setAttribute('aria-expanded', 'false')
})
}
}
})
})
}
const linkArrows = (quicklinksContainer) => {
const subNavItems = quicklinksContainer.querySelectorAll('.uol-quicklinks__group__link')
// Add arrows to subnav links
subNavItems.forEach( (item) => {
// Split text to array
const innerTextArray = item.innerText.trim().split(' ')
// Wrap last word and svg in no-wrap span to avoid wrapping
item.innerHTML = innerTextArray.slice(0, -1).join(' ') + ' <span class="no-wrap">' + innerTextArray[innerTextArray.length - 1] + '' + svgRightArrow + '</span>'
})
}
const quicklinksContainers = document.querySelectorAll('.uol-quicklinks');
quicklinksContainers.forEach( (quicklinksContainer) => {
// Store original HTML
const quicklinksContainerClone = quicklinksContainer.cloneNode(true)
// Initialise
groupButtons(quicklinksContainer)
moreWrap(quicklinksContainer)
buttonAction(quicklinksContainer)
focusListener(quicklinksContainer)
linkArrows(quicklinksContainer)
// Reset on resize
let windowWidth = false
window.addEventListener('resize', () => {
setTimeout(() => {
// Has the width changed?
if (!windowWidth || window.innerWidth != windowWidth) {
// Reset to original HTML
quicklinksContainer.innerHTML = quicklinksContainerClone.innerHTML
// Rerun
groupButtons(quicklinksContainer)
moreWrap(quicklinksContainer)
buttonAction(quicklinksContainer)
focusListener(quicklinksContainer)
linkArrows(quicklinksContainer)
windowWidth = window.innerWidth
}
}, 100);
})
// Close on click outside
document.addEventListener("click", (evt) => {
let targetElement = evt.target; // clicked element
do {
if (targetElement == quicklinksContainer) {
// This is a click inside. Do nothing, just return.
return;
}
// Go up the DOM
targetElement = targetElement.parentNode;
} while (targetElement)
// This is a click outside.
const buttons = quicklinksContainer.querySelectorAll('.uol-quicklinks__button')
quicklinksContainer.parentNode.classList.remove('uol-global-masthead--quicklinks-expanded')
buttons.forEach( (button) => {
button.parentNode.classList.remove('uol-quicklinks__group--expanded')
button.parentNode.classList.remove('uol-quicklinks__group--sibling-expanded')
button.setAttribute('aria-expanded', 'false')
})
})
// Close on escape
window.addEventListener("keydown", function(event) {
if (event.key === 'Escape' || event.keyCode === 27) {
const buttons = quicklinksContainer.querySelectorAll('.uol-quicklinks__button')
quicklinksContainer.parentNode.classList.remove('uol-global-masthead--quicklinks-expanded')
buttons.forEach( (button) => {
button.parentNode.classList.remove('uol-quicklinks__group--expanded')
button.parentNode.classList.remove('uol-quicklinks__group--sibling-expanded')
button.setAttribute('aria-expanded', 'false')
})
}
}, true);
})
}
{
"quicklinks": [
{
"title": "Students",
"links": [
{
"title": "Mobile App",
"url": "/mobile-app"
},
{
"title": "Minerva",
"url": "/minerva"
},
{
"title": "For Students",
"url": "/for-students"
},
{
"title": "Library",
"url": "https://library.leeds.ac.uk"
},
{
"title": "IT",
"url": "/it"
},
{
"title": "Campus map",
"url": "/campus-map"
}
]
},
{
"title": "Staff",
"links": [
{
"title": "For Staff",
"url": "/staff/for-staff"
},
{
"title": "Services A-Z",
"url": "/staff/services-a-z"
},
{
"title": "Staff A-Z",
"url": "/staff/staff-a-z"
},
{
"title": "Faculties A-Z",
"url": "/staff/faculty-a-z"
},
{
"title": "Student Education Service",
"url": "/staff/student-education-service"
},
{
"title": "HR",
"url": "/staff/hr"
},
{
"title": "IT",
"url": "/staff/it"
},
{
"title": "Campus map",
"url": "/campus-map"
}
]
},
{
"title": "Faculties",
"links": [
{
"title": "Faculty of Arts, Humanities and Cultures",
"url": "/faculty/arts-humanities-cultures"
},
{
"title": "Faculty of Biological Sciences",
"url": "/faculty/biological-sciences"
},
{
"title": "Faculty of Business",
"url": "/faculty/business"
},
{
"title": "Faculty of Engineering and Physical Sciences",
"url": "/faculty/engineering-physical-sciences"
},
{
"title": "Faculty of Environment",
"url": "/faculty/environment"
},
{
"title": "Faculty of Medicine and Health",
"url": "/faculty/medicine-health"
},
{
"title": "Faculty of Social Sciences",
"url": "/faculty/social-sciences"
}
]
}
]
}