<!--
Related blog post:
https://css-tricks.com/container-adapting-tabs-with-more-button/
-->
<nav class="tabs">
<ul class="-primary">
<li>
<a target="_blank" href="https://en.wikipedia.org/wiki/Falkenberg">
Falkenberg
</a>
<li>
<a target="_blank" href="https://en.wikipedia.org/wiki/Braga">
Braga
</a>
<li>
<a target="_blank" href="https://en.wikipedia.org/wiki/Stockholm">
Stockholm
</a>
<li>
<a target="_blank" href="https://en.wikipedia.org/wiki/Trnava">
Trnava
</a>
<li>
<a target="_blank" href="https://en.wikipedia.org/wiki/Plovdiv">
Plovdiv
</a>
<li>
<a target="_blank" href="https://en.wikipedia.org/wiki/Klaip%C4%97da">
Klaipėda
</a>
<li>
<a target="_blank" href="https://en.wikipedia.org/wiki/Punta_Cana">
Punta Cana
</a>
<li>
<a target="_blank" href="https://en.wikipedia.org/wiki/Lisbon">
Lisbon
</a>
</ul>
</nav>
// defaults
$color-bg: #D51E49;
$color-bg--dark: darken($color-bg, 7%);
$color-bg--darker: darken($color-bg, 14%);
$color-text: #FAF3DD;
*, *::before, *::after {
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
background-color: $color-text;
padding: 2em;
}
a {
text-decoration: none;
}
button {
cursor: pointer;
border: 0;
padding: 0;
}
// tabs
.tabs {
position: relative;
&:not(.--jsfied) {
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
// shared
.--hidden {
display: none;
}
a,
button {
width: 100%;
height: 100%;
display: block;
font-size: 1em;
line-height: 1.2;
text-align: center;
color: $color-text;
background-color: transparent;
}
// primary
.-primary {
display: flex;
> li {
flex-grow: 1;
background-color: $color-bg;
+ li {
border-left: 1px solid $color-bg--dark;
}
> a,
> button {
white-space: nowrap;
padding: 1em 0.6em;
box-shadow: inset 0 -0.2em 0 $color-bg--dark;
&:hover { background-color: $color-bg--dark; }
&:active { background-color: $color-bg--darker; }
}
}
.-more {
background-color: $color-bg--dark;
> button span {
display: inline-block;
transition: transform 0.2s;
}
}
}
&.--show-secondary .-primary {
.-more > button span {
transform: rotate(180deg);
}
}
// secondary
.-secondary {
max-width: 100%;
min-width: 10em;
display: none;
position: absolute;
top: 100%;
right: 0;
box-shadow: 0 0.3em 0.5em rgba(#000, 0.3);
animation: nav-secondary 0.2s;
li {
border-top: 1px solid $color-bg;
background-color: $color-bg--dark;
}
a,
button {
padding: 0.6em;
&:hover { background-color: $color-bg--darker; }
&:active { background-color: $color-bg; }
}
}
&.--show-secondary .-secondary {
display: block;
}
}
// keyframes
@keyframes nav-secondary {
0% {
opacity: 0;
transform: translateY(-1em);
}
100% {
opacity: 1;
transform: translateY(0);
}
}
View Compiled
/*
Consider using these polyfills to broaden browser support:
— https://www.npmjs.com/package/classlist-polyfill
— https://www.npmjs.com/package/nodelist-foreach-polyfill
*/
const container = document.querySelector('.tabs')
const primary = container.querySelector('.-primary')
const primaryItems = container.querySelectorAll('.-primary > li:not(.-more)')
container.classList.add('--jsfied')
// insert "more" button and duplicate the list
primary.insertAdjacentHTML('beforeend', `
<li class="-more">
<button type="button" aria-haspopup="true" aria-expanded="false">
More <span>↓</span>
</button>
<ul class="-secondary">
${primary.innerHTML}
</ul>
</li>
`)
const secondary = container.querySelector('.-secondary')
const secondaryItems = secondary.querySelectorAll('li')
const allItems = container.querySelectorAll('li')
const moreLi = primary.querySelector('.-more')
const moreBtn = moreLi.querySelector('button')
moreBtn.addEventListener('click', (e) => {
e.preventDefault()
container.classList.toggle('--show-secondary')
moreBtn.setAttribute('aria-expanded', container.classList.contains('--show-secondary'))
})
// adapt tabs
const doAdapt = () => {
// reveal all items for the calculation
allItems.forEach((item) => {
item.classList.remove('--hidden')
})
// hide items that won't fit in the Primary
let stopWidth = moreBtn.offsetWidth
let hiddenItems = []
const primaryWidth = primary.offsetWidth
primaryItems.forEach((item, i) => {
if(primaryWidth >= stopWidth + item.offsetWidth) {
stopWidth += item.offsetWidth
} else {
item.classList.add('--hidden')
hiddenItems.push(i)
}
})
// toggle the visibility of More button and items in Secondary
if(!hiddenItems.length) {
moreLi.classList.add('--hidden')
container.classList.remove('--show-secondary')
moreBtn.setAttribute('aria-expanded', false)
}
else {
secondaryItems.forEach((item, i) => {
if(!hiddenItems.includes(i)) {
item.classList.add('--hidden')
}
})
}
}
doAdapt() // adapt immediately on load
window.addEventListener('resize', doAdapt) // adapt on window resize
// hide Secondary on the outside click
document.addEventListener('click', (e) => {
let el = e.target
while(el) {
if(el === secondary || el === moreBtn) {
return;
}
el = el.parentNode
}
container.classList.remove('--show-secondary')
moreBtn.setAttribute('aria-expanded', false)
})
View Compiled
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.