
A card-style expandable & collapsible fullscreen navigation menu made with JavaScript and CSS/CSS3.
How to use it:
1. Code the HTML for the card menu.
<nav class="nav">
<ul class="nav__items">
<li class="nav__item nav__item--active">
<a class="nav__item-link" href="#">
<svg class="nav__item-icon" width="32px" height="32px" aria-hidden="true">
<use xlink:href="#home" />
</svg>
Home
</a>
</li>
<li class="nav__item">
<a class="nav__item-link" href="#">
<svg class="nav__item-icon" width="32px" height="32px" aria-hidden="true">
<use xlink:href="#portfolio" />
</svg>
Portfolio
</a>
</li>
<li class="nav__item">
<a class="nav__item-link" href="#">
<svg class="nav__item-icon" width="32px" height="32px" aria-hidden="true">
<use xlink:href="#about" />
</svg>
About
</a>
</li>
<li class="nav__item">
<a class="nav__item-link" href="#">
<svg class="nav__item-icon" width="32px" height="32px" aria-hidden="true">
<use xlink:href="#contact" />
</svg>
Contact
</a>
</li>
</ul>
<svg class="nav__arrow" width="12px" height="6px" aria-hidden="true">
<polyline fill="none" stroke="currentColor" stroke-width="2" points="1,1 6,5 11,1" />
</svg>
</nav><!-- SVG Icons -->
<svg display="none">
<symbol id="home" viewBox="0 0 32 32">
<g fill="currentColor">
<polygon points="16 0,0 10,0 32,10 32,10 16,22 16,22 32,32 32,32 10"/>
</g>
</symbol>
<symbol id="portfolio" viewBox="0 0 32 32">
<g fill="currentColor">
<path d="M30,8h-6v-2c0-1.1-.9-2-2-2H10c-1.1,0-2,.9-2,2v2H2c-1.1,0-2,.9-2,2V26c0,1.1,.9,2,2,2H30c1.1,0,2-.9,2-2V10c0-1.1-.9-2-2-2Zm-20-1c0-.55,.45-1,1-1h10c.55,0,1,.45,1,1v1H10v-1Z"/>
</g>
</symbol>
<symbol id="about" viewBox="0 0 32 32">
<g fill="currentColor">
<path d="M16,0C7.163,0,0,7.163,0,16s7.163,16,16,16,16-7.163,16-16S24.837,0,16,0Zm2,22c0,1.1-.9,2-2,2s-2-.9-2-2v-6c0-1.1,.9-2,2-2s2,.9,2,2v6Zm-2-10c-1.105,0-2-.895-2-2s.895-2,2-2,2,.895,2,2-.895,2-2,2Z"/>
</g>
</symbol>
<symbol id="contact" viewBox="0 0 32 32">
<g fill="currentColor">
<path d="M30,4H2c-1.1,0-2,.9-2,2v14c0,1.1,.9,2,2,2h3.169l5.417,5.417c.778,.778,2.051,.778,2.828,0l5.417-5.417h11.169c1.1,0,2-.9,2-2V6c0-1.1-.9-2-2-2ZM5,8h6c.55,0,1,.45,1,1s-.45,1-1,1H5c-.55,0-1-.45-1-1s.45-1,1-1Zm0,4h14c.55,0,1,.45,1,1s-.45,1-1,1H5c-.55,0-1-.45-1-1s.45-1,1-1Zm22,6H5c-.55,0-1-.45-1-1s.45-1,1-1H27c.55,0,1,.45,1,1s-.45,1-1,1Z"/>
</g>
</symbol>
</svg>2. The required CSS styles.
:root {
--hue: 223;
--bg: hsl(var(--hue),10%,90%);
--fg: hsl(var(--hue),10%,10%);
--primary: hsl(var(--hue),90%,55%);
--trans-dur: 0.3s;
font-size: calc(16px + (20 - 16) * (100vw - 320px) / (1280 - 320));
}
.no-scroll {
overflow: hidden;
}
.nav {
position: fixed;
top: 0;
text-align: center;
text-transform: uppercase;
width: 100vw;
}
.nav__arrow,
.nav__items {
z-index: 0;
}
.nav__arrow,
.nav__item {
color: hsl(0,0%,0%,0.7);
}
.nav__arrow {
display: block;
pointer-events: none;
position: absolute;
top: 3em;
left: calc(50% - 0.375em);
width: 0.75em;
height: 0.375em;
transition:
opacity 0.15s 0.15s ease-in-out,
transform 0.15s 0.15s ease-in-out;
}
.nav__items {
list-style: none;
position: relative;
width: inherit;
}
.nav__item {
background-color: hsl(var(--hue),90%,70%);
box-shadow: 0 0 0 hsla(0,0%,0%,0.3);
font-size: 0.75em;
font-weight: 600;
letter-spacing: 0.25em;
position: absolute;
width: 100%;
height: 25vh;
min-height: 8rem;
transition:
box-shadow var(--trans-dur) ease-in-out,
transform var(--trans-dur) ease-in-out,
visibility var(--trans-dur) steps(1);
transform: translateY(calc(-100% + 4rem));
visibility: hidden;
z-index: 0;
}
.nav__item:nth-of-type(2) {
background-color: hsl(3,90%,70%);
z-index: -1;
}
.nav__item:nth-of-type(3) {
background-color: hsl(33,90%,70%);
z-index: -2;
}
.nav__item:nth-of-type(4) {
background-color: hsl(153,90%,40%);
z-index: -3;
}
.nav__item-link {
background-color: hsla(0,0%,100%,0);
color: inherit;
display: flex;
flex-direction: column;
justify-content: flex-end;
align-items: center;
padding: 1.5rem;
text-decoration: none;
transition: background-color 0.15s ease-in-out;
width: 100%;
height: 100%;
}
.nav__item-link:focus {
background-color: hsla(0,0%,100%,0.2);
outline: transparent;
}
.nav__item-icon {
display: block;
margin: 0 auto 1.5em;
opacity: 0;
pointer-events: none;
width: 2em;
height: 2em;
transition:
opacity var(--trans-dur) ease-in-out,
transform var(--trans-dur) ease-in-out;
transform: scale(0);
}
.nav--open {
overflow-x: hidden;
overflow-y: auto;
height: 100%;
}
.nav--open .nav__arrow {
opacity: 0;
transform: scale(0);
transition-delay: 0s;
}
.nav--open .nav__item-icon {
opacity: 1;
transform: scale(1);
transition-delay: 0.05s;
transition-timing-function: cubic-bezier(0.42,0,0.58,1.5);
}
.nav--open .nav__item {
box-shadow: 0 0.5em 0.5em hsla(0,0%,0%,0.3);
transform: translateY(0);
transition-duration: var(--trans-dur), var(--trans-dur), 0s;
visibility: visible;
}
.nav--open .nav__item:nth-of-type(2) {
transform: translateY(100%);
}
.nav--open .nav__item:nth-of-type(2) .nav__item-icon {
transition-delay: 0.1s;
}
.nav--open .nav__item:nth-of-type(3) {
transform: translateY(200%);
}
.nav--open .nav__item:nth-of-type(3) .nav__item-icon {
transition-delay: 0.15s;
}
.nav--open .nav__item:nth-of-type(4) {
transform: translateY(300%);
}
.nav--open .nav__item:nth-of-type(4) .nav__item-icon {
transition-delay: 0.2s;
}
.nav:not(.nav--open) .nav__item--active {
box-shadow: 0 0.5em 0.5em hsla(0,0%,0%,0.3);
visibility: visible;
z-index: 1;
}
/* `:focus-visible` support */
@supports selector(:focus-visible) {
.nav__item-link:focus {
background-color: hsla(0,0%,100%,0);
}
.nav__item-link:focus-visible {
background-color: hsla(0,0%,100%,0.2);
}
}
/* Dark theme */
@media (prefers-color-scheme: dark) {
:root {
--bg: hsl(var(--hue),10%,20%);
--fg: hsl(var(--hue),10%,90%);
}
}3. Initialize the card menu.
window.addEventListener("DOMContentLoaded",() => {
const nav = new CardNav("nav");
});
class CardNav {
constructor(qs) {
this.overflowClass ="no-scroll";
this.openClass = "nav--open";
this.activeClass = "nav__item--active";
this.el = document.querySelector(qs);
this.el?.addEventListener("click",this.toggle.bind(this));
}
toggle(e) {
e.preventDefault();
const { target } = e;
if (target.hasAttribute("href")) {
const { body } = document;
const { overflowClass, openClass, activeClass, el } = this;
// toggle class to open or close
el.classList.toggle(openClass);
if (el.classList.contains(openClass)) {
body.classList.add(overflowClass);
} else {
body.classList.remove(overflowClass);
// take the class from the previously active item…
const active = el?.querySelector(`.${activeClass}`);
active.classList.remove(activeClass);
// …and give it to the newly active item
target.parentElement.classList.add(activeClass);
}
}
}
}






