NavigationSimpleApril 7, 2025

Nav Panel Height Expand

A hidden nav panel animates from height:0 to height:auto when the menu is opened. Simultaneously fades in. Collapses back on close. Source: itsjay.us nav dock menu open.

View Full Demo →
demo.jsx
import NavPanelHeightExpand from "./index.jsx";
import styles from "./demo.module.css";

const props = {
  siteName: "No-5 Studio",
  links: [
    { label: "Work", href: "#" },
    { label: "About", href: "#" },
    { label: "Services", href: "#" },
    { label: "Contact", href: "#" },
  ],
};

export default function NavPanelHeightExpandDemo() {
  return (
    <div className={styles.demo}>
      <NavPanelHeightExpand {...props} />
      <div className={styles.hero}>
        <p className={styles.label}>Portfolio — 2024</p>
        <h1 className={styles.heading}>Selected Work</h1>
        <p className={styles.sub}>Interaction &amp; Visual Design</p>
      </div>
    </div>
  );
}
index.jsx
"use client";

import { useState } from "react";
import { motion, AnimatePresence } from "framer-motion";
import styles from "./styles.module.css";

export default function NavPanelHeightExpand({ siteName = "No-5 Studio", links = [] }) {
  const [isOpen, setIsOpen] = useState(false);

  return (
    <div className={styles.wrapper}>
      <nav className={styles.navbar}>
        <span className={styles.siteName}>{siteName}</span>
        <button
          className={styles.toggle}
          onClick={() => setIsOpen((prev) => !prev)}
          aria-expanded={isOpen}
          aria-label="Toggle menu"
        >
          <span className={`${styles.toggleLine} ${isOpen ? styles.open : ""}`} />
          <span className={`${styles.toggleLine} ${isOpen ? styles.open : ""}`} />
        </button>
      </nav>

      <motion.div
        animate={{ height: isOpen ? "auto" : 0, opacity: isOpen ? 1 : 0 }}
        initial={{ height: 0, opacity: 0 }}
        transition={{ duration: 0.5, ease: [0.16, 1, 0.3, 1] }}
        className={styles.panel}
      >
        <ul className={styles.linkList}>
          {links.map((link, i) => (
            <motion.li
              key={link.label}
              initial={{ opacity: 0, y: 8 }}
              animate={isOpen ? { opacity: 1, y: 0 } : { opacity: 0, y: 8 }}
              transition={{
                duration: 0.4,
                ease: [0.16, 1, 0.3, 1],
                delay: isOpen ? 0.15 + i * 0.06 : 0,
              }}
            >
              <a href={link.href} className={styles.link}>
                {link.label}
              </a>
            </motion.li>
          ))}
        </ul>
      </motion.div>
    </div>
  );
}
demo.module.css
.demo {
  min-height: 100vh;
  background: #f8f8f6;
  display: flex;
  flex-direction: column;
}

.hero {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 4rem 2rem;
  gap: 1rem;
}

.label {
  font-size: 0.75rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: #9b9b9b;
}

.heading {
  font-size: clamp(3rem, 8vw, 7rem);
  font-weight: 500;
  letter-spacing: -0.03em;
  line-height: 0.95;
  color: #1a1a1a;
}

.sub {
  font-size: 1rem;
  font-weight: 400;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: #9b9b9b;
}
styles.module.css
.wrapper {
  width: 100%;
  background-color: #ffffff;
  border-bottom: 1px solid #e5e5e5;
}

.navbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 1.25rem 2rem;
  max-width: 1100px;
  margin: 0 auto;
}

.siteName {
  font-size: 1rem;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: #1a1a1a;
}

.toggle {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 5px;
  width: 28px;
  height: 28px;
  cursor: pointer;
  background: none;
  border: none;
  padding: 0;
}

.toggleLine {
  display: block;
  width: 100%;
  height: 1.5px;
  background-color: #1a1a1a;
  transition: transform 0.3s ease, opacity 0.3s ease;
  transform-origin: center;
}

.toggleLine.open:first-child {
  transform: translateY(3.25px) rotate(45deg);
}

.toggleLine.open:last-child {
  transform: translateY(-3.25px) rotate(-45deg);
}

.panel {
  overflow: hidden;
  border-top: 1px solid #e5e5e5;
}

.linkList {
  display: flex;
  flex-direction: column;
  max-width: 1100px;
  margin: 0 auto;
  padding: 1rem 2rem 1.5rem;
  gap: 0.25rem;
}

.link {
  display: block;
  font-size: 2rem;
  font-weight: 500;
  letter-spacing: -0.02em;
  color: #1a1a1a;
  text-decoration: none;
  padding: 0.375rem 0;
  transition: color 200ms ease;
}

.link:hover {
  color: #6b6b6b;
}
  • framer-motion

May 4, 2026

NAVIGATION

Nav Slide Panel

A minimal header that slides in from the top on load. Desktop shows logo, nav links, language switcher, and underlined contact CTA. Mobile collapses to logo + menu toggle with a full-screen white panel featuring large stagger-animated nav links, office locations, language selector, and copyright.

Apr 27, 2026

NAVIGATION

Nav Agency Grid

A fixed agency-style navigation bar using a 4-column CSS grid with staggered GSAP entrance animations, current-page indicator with animated dash, scroll-direction hide/show, desktop hover dropdown, and a full-screen mobile menu with large typography.

Apr 24, 2026

NAVIGATION

Nav Glassmorphic

A floating glassmorphic navigation bar with frosted semi-transparent background, centered nav links, locale selector, and outlined CTA button. Collapses to logo + hamburger on mobile with an expanding panel featuring nav links, social links, and email.